Merge branch 'memory'

This commit is contained in:
Ben Vanik
2015-05-19 20:29:00 -07:00
51 changed files with 1978 additions and 871 deletions

View File

@@ -1673,6 +1673,49 @@ EMITTER_OPCODE_TABLE(
PREFETCH);
// ============================================================================
// OPCODE_MEMSET
// ============================================================================
EMITTER(MEMSET_I64_I8_I64, MATCH(I<OPCODE_MEMSET, VoidOp, I64<>, I8<>, I64<>>)) {
static void Emit(X64Emitter& e, const EmitArgType& i) {
assert_true(i.src2.is_constant);
assert_true(i.src3.is_constant);
assert_true(i.src2.constant() == 0);
e.vpxor(e.xmm0, e.xmm0);
auto addr = ComputeMemoryAddress(e, i.src1);
switch (i.src3.constant()) {
case 32:
e.vmovaps(e.ptr[addr + 0 * 16], e.xmm0);
e.vmovaps(e.ptr[addr + 1 * 16], e.xmm0);
break;
case 128:
e.vmovaps(e.ptr[addr + 0 * 16], e.xmm0);
e.vmovaps(e.ptr[addr + 1 * 16], e.xmm0);
e.vmovaps(e.ptr[addr + 2 * 16], e.xmm0);
e.vmovaps(e.ptr[addr + 3 * 16], e.xmm0);
e.vmovaps(e.ptr[addr + 4 * 16], e.xmm0);
e.vmovaps(e.ptr[addr + 5 * 16], e.xmm0);
e.vmovaps(e.ptr[addr + 6 * 16], e.xmm0);
e.vmovaps(e.ptr[addr + 7 * 16], e.xmm0);
break;
default:
assert_unhandled_case(i.src3.constant());
break;
}
if (IsTracingData()) {
addr = ComputeMemoryAddress(e, i.src1);
e.mov(e.r9, i.src3.constant());
e.mov(e.r8, i.src2.constant());
e.lea(e.rdx, e.ptr[addr]);
e.CallNative(reinterpret_cast<void*>(TraceMemset));
}
}
};
EMITTER_OPCODE_TABLE(
OPCODE_MEMSET,
MEMSET_I64_I8_I64);
// ============================================================================
// OPCODE_MAX
// ============================================================================
@@ -6335,6 +6378,7 @@ void RegisterSequences() {
REGISTER_EMITTER_OPCODE_TABLE(OPCODE_STORE_CONTEXT);
REGISTER_EMITTER_OPCODE_TABLE(OPCODE_LOAD);
REGISTER_EMITTER_OPCODE_TABLE(OPCODE_STORE);
REGISTER_EMITTER_OPCODE_TABLE(OPCODE_MEMSET);
REGISTER_EMITTER_OPCODE_TABLE(OPCODE_PREFETCH);
REGISTER_EMITTER_OPCODE_TABLE(OPCODE_MAX);
REGISTER_EMITTER_OPCODE_TABLE(OPCODE_VECTOR_MAX);

View File

@@ -28,10 +28,11 @@ namespace x64 {
#define TARGET_THREAD 1
#define IFLUSH() fflush(stdout)
#define IFLUSH() \
if (thread_state->thread_id() == TARGET_THREAD) fflush(stdout)
#define IPRINT \
if (thread_state->thread_id() == TARGET_THREAD) printf
#define DFLUSH() fflush(stdout)
#define DFLUSH() IFLUSH()
#define DPRINT \
DFLUSH(); \
if (thread_state->thread_id() == TARGET_THREAD) printf
@@ -194,6 +195,13 @@ void TraceMemoryStoreV128(void* raw_context, uint32_t address, __m128 value) {
xe::m128_i32<3>(value));
}
void TraceMemset(void* raw_context, uint32_t address, uint8_t value,
uint32_t length) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("memset %.8X-%.8X (%d) = %.2X", address, address + length, length,
value);
}
} // namespace x64
} // namespace backend
} // namespace cpu

View File

@@ -64,6 +64,9 @@ void TraceMemoryStoreF32(void* raw_context, uint32_t address, __m128 value);
void TraceMemoryStoreF64(void* raw_context, uint32_t address, __m128 value);
void TraceMemoryStoreV128(void* raw_context, uint32_t address, __m128 value);
void TraceMemset(void* raw_context, uint32_t address, uint8_t value,
uint32_t length);
} // namespace x64
} // namespace backend
} // namespace cpu

View File

@@ -984,11 +984,24 @@ XEEMITTER(dcbtst, 0x7C0001EC, X)(PPCHIRBuilder& f, InstrData& i) {
}
XEEMITTER(dcbz, 0x7C0007EC, X)(PPCHIRBuilder& f, InstrData& i) {
// No-op for now.
// TODO(benvanik): use prefetch
// or dcbz128 0x7C2007EC
// XEINSTRNOTIMPLEMENTED();
f.Nop();
// EA <- (RA) + (RB)
// memset(EA & ~31, 0, 32)
Value* ea = CalculateEA_0(f, i.X.RA, i.X.RB);
int block_size;
int address_mask;
if (i.X.RT == 1) {
// dcbz128 - 128 byte set
block_size = 128;
address_mask = ~127;
}
else {
// dcbz - 32 byte set
block_size = 32;
address_mask = ~31;
}
f.Memset(f.And(ea, f.LoadConstant(int64_t(address_mask))),
f.LoadZero(INT8_TYPE), f.LoadConstant(int64_t(block_size)));
return 0;
}

View File

@@ -197,9 +197,10 @@ class TestRunner {
// Simulate a thread.
uint32_t stack_size = 64 * 1024;
uint32_t stack_address = START_ADDRESS - stack_size;
uint32_t thread_state_address = stack_address - 0x1000;
thread_state.reset(new ThreadState(processor.get(), 0x100, stack_address,
stack_size, thread_state_address));
uint32_t pcr_address = stack_address - 0x1000;
thread_state.reset(new ThreadState(processor.get(), 0x100,
ThreadStackType::kUserStack,
stack_address, stack_size, pcr_address));
return true;
}

View File

@@ -20,15 +20,27 @@ namespace xe {
namespace cpu {
namespace hir {
#define ASSERT_ADDRESS_TYPE(value)
#define ASSERT_INTEGER_TYPE(value)
#define ASSERT_FLOAT_TYPE(value)
#define ASSERT_NON_VECTOR_TYPE(value)
#define ASSERT_VECTOR_TYPE(value)
#define ASSERT_ADDRESS_TYPE(value) \
\
assert_true((value->type) == INT32_TYPE || (value->type) == INT64_TYPE)
#define ASSERT_INTEGER_TYPE(value) \
\
assert_true((value->type) == INT8_TYPE || (value->type) == INT16_TYPE || \
(value->type) == INT32_TYPE || (value->type) == INT64_TYPE)
#define ASSERT_FLOAT_TYPE(value) \
assert_true((value->type) == FLOAT32_TYPE || (value->type) == FLOAT64_TYPE)
#define ASSERT_NON_FLOAT_TYPE(value) \
\
assert_true((value->type) != FLOAT32_TYPE && (value->type) != FLOAT64_TYPE)
#define ASSERT_NON_VECTOR_TYPE(value) assert_false((value->type) == VEC128_TYPE)
#define ASSERT_VECTOR_TYPE(value) assert_true((value->type) == VEC128_TYPE)
#define ASSERT_FLOAT_OR_VECTOR_TYPE(value) \
assert_true((value->type) == FLOAT32_TYPE || \
(value->type) == FLOAT64_TYPE || (value->type) == VEC128_TYPE)
#define ASSERT_TYPES_EQUAL(value1, value2) \
assert_true((value1->type) == (value2->type))
HIRBuilder::HIRBuilder() {
HIRBuilder::HIRBuilder() {
arena_ = new Arena();
Reset();
}
@@ -755,7 +767,7 @@ void HIRBuilder::ReturnTrue(Value* cond) {
return;
}
ASSERT_ADDRESS_TYPE(value);
ASSERT_ADDRESS_TYPE(cond);
Instr* i = AppendInstr(OPCODE_RETURN_TRUE_info, 0);
i->set_src1(cond);
i->src2.value = i->src3.value = NULL;
@@ -873,8 +885,9 @@ Value* HIRBuilder::SignExtend(Value* value, TypeName target_type) {
}
Value* HIRBuilder::Truncate(Value* value, TypeName target_type) {
ASSERT_INTEGER_TYPE(value->type);
ASSERT_INTEGER_TYPE(target_type);
ASSERT_INTEGER_TYPE(value);
assert_true(target_type == INT8_TYPE || target_type == INT16_TYPE ||
target_type == INT32_TYPE || target_type == INT64_TYPE);
if (value->type == target_type) {
return value;
@@ -908,7 +921,7 @@ Value* HIRBuilder::Convert(Value* value, TypeName target_type,
}
Value* HIRBuilder::Round(Value* value, RoundMode round_mode) {
ASSERT_FLOAT_TYPE(value);
ASSERT_FLOAT_OR_VECTOR_TYPE(value);
if (value->IsConstant()) {
Value* dest = CloneValue(value);
@@ -1090,6 +1103,16 @@ void HIRBuilder::Store(Value* address, Value* value, uint32_t store_flags) {
i->src3.value = NULL;
}
void HIRBuilder::Memset(Value* address, Value* value, Value* length) {
ASSERT_ADDRESS_TYPE(address);
ASSERT_TYPES_EQUAL(address, length);
assert_true(value->type == INT8_TYPE);
Instr* i = AppendInstr(OPCODE_MEMSET_info, 0);
i->set_src1(address);
i->set_src2(value);
i->set_src3(length);
}
void HIRBuilder::Prefetch(Value* address, size_t length,
uint32_t prefetch_flags) {
ASSERT_ADDRESS_TYPE(address);
@@ -1471,8 +1494,6 @@ Value* HIRBuilder::MulSub(Value* value1, Value* value2, Value* value3) {
}
Value* HIRBuilder::Neg(Value* value) {
ASSERT_NON_VECTOR_TYPE(value);
Instr* i = AppendInstr(OPCODE_NEG_info, 0, AllocValue(value->type));
i->set_src1(value);
i->src2.value = i->src3.value = NULL;
@@ -1480,7 +1501,7 @@ Value* HIRBuilder::Neg(Value* value) {
}
Value* HIRBuilder::Abs(Value* value) {
ASSERT_NON_VECTOR_TYPE(value);
ASSERT_FLOAT_OR_VECTOR_TYPE(value);
Instr* i = AppendInstr(OPCODE_ABS_info, 0, AllocValue(value->type));
i->set_src1(value);
@@ -1489,7 +1510,7 @@ Value* HIRBuilder::Abs(Value* value) {
}
Value* HIRBuilder::Sqrt(Value* value) {
ASSERT_FLOAT_TYPE(value);
ASSERT_FLOAT_OR_VECTOR_TYPE(value);
Instr* i = AppendInstr(OPCODE_SQRT_info, 0, AllocValue(value->type));
i->set_src1(value);
@@ -1498,7 +1519,7 @@ Value* HIRBuilder::Sqrt(Value* value) {
}
Value* HIRBuilder::RSqrt(Value* value) {
ASSERT_FLOAT_TYPE(value);
ASSERT_FLOAT_OR_VECTOR_TYPE(value);
Instr* i = AppendInstr(OPCODE_RSQRT_info, 0, AllocValue(value->type));
i->set_src1(value);
@@ -1507,7 +1528,7 @@ Value* HIRBuilder::RSqrt(Value* value) {
}
Value* HIRBuilder::Pow2(Value* value) {
ASSERT_FLOAT_TYPE(value);
ASSERT_FLOAT_OR_VECTOR_TYPE(value);
Instr* i = AppendInstr(OPCODE_POW2_info, 0, AllocValue(value->type));
i->set_src1(value);
@@ -1516,7 +1537,7 @@ Value* HIRBuilder::Pow2(Value* value) {
}
Value* HIRBuilder::Log2(Value* value) {
ASSERT_FLOAT_TYPE(value);
ASSERT_FLOAT_OR_VECTOR_TYPE(value);
Instr* i = AppendInstr(OPCODE_LOG2_info, 0, AllocValue(value->type));
i->set_src1(value);
@@ -1551,8 +1572,8 @@ Value* HIRBuilder::DotProduct4(Value* value1, Value* value2) {
}
Value* HIRBuilder::And(Value* value1, Value* value2) {
ASSERT_INTEGER_TYPE(value1);
ASSERT_INTEGER_TYPE(value2);
ASSERT_NON_FLOAT_TYPE(value1);
ASSERT_NON_FLOAT_TYPE(value2);
ASSERT_TYPES_EQUAL(value1, value2);
if (value1 == value2) {
@@ -1571,8 +1592,8 @@ Value* HIRBuilder::And(Value* value1, Value* value2) {
}
Value* HIRBuilder::Or(Value* value1, Value* value2) {
ASSERT_INTEGER_TYPE(value1);
ASSERT_INTEGER_TYPE(value2);
ASSERT_NON_FLOAT_TYPE(value1);
ASSERT_NON_FLOAT_TYPE(value2);
ASSERT_TYPES_EQUAL(value1, value2);
if (value1 == value2) {
@@ -1591,8 +1612,8 @@ Value* HIRBuilder::Or(Value* value1, Value* value2) {
}
Value* HIRBuilder::Xor(Value* value1, Value* value2) {
ASSERT_INTEGER_TYPE(value1);
ASSERT_INTEGER_TYPE(value2);
ASSERT_NON_FLOAT_TYPE(value1);
ASSERT_NON_FLOAT_TYPE(value2);
ASSERT_TYPES_EQUAL(value1, value2);
if (value1 == value2) {
@@ -1607,7 +1628,7 @@ Value* HIRBuilder::Xor(Value* value1, Value* value2) {
}
Value* HIRBuilder::Not(Value* value) {
ASSERT_INTEGER_TYPE(value);
ASSERT_NON_FLOAT_TYPE(value);
if (value->IsConstant()) {
Value* dest = CloneValue(value);
@@ -1657,7 +1678,7 @@ Value* HIRBuilder::VectorShl(Value* value1, Value* value2, TypeName part_type) {
}
Value* HIRBuilder::Shr(Value* value1, Value* value2) {
ASSERT_INTEGER_TYPE(value1);
ASSERT_NON_FLOAT_TYPE(value1);
ASSERT_INTEGER_TYPE(value2);
if (value2->IsConstantZero()) {

View File

@@ -132,6 +132,7 @@ class HIRBuilder {
Value* Load(Value* address, TypeName type, uint32_t load_flags = 0);
void Store(Value* address, Value* value, uint32_t store_flags = 0);
void Memset(Value* address, Value* value, Value* length);
void Prefetch(Value* address, size_t length, uint32_t prefetch_flags = 0);
Value* Max(Value* value1, Value* value2);

View File

@@ -142,6 +142,7 @@ enum Opcode {
OPCODE_STORE_CONTEXT,
OPCODE_LOAD,
OPCODE_STORE,
OPCODE_MEMSET,
OPCODE_PREFETCH,
OPCODE_MAX,
OPCODE_VECTOR_MAX,

View File

@@ -224,6 +224,12 @@ DEFINE_OPCODE(
OPCODE_SIG_X_V_V,
OPCODE_FLAG_MEMORY)
DEFINE_OPCODE(
OPCODE_MEMSET,
"memset",
OPCODE_SIG_X_V_V_V,
0)
DEFINE_OPCODE(
OPCODE_PREFETCH,
"prefetch",

View File

@@ -12,6 +12,7 @@
#include "xenia/base/assert.h"
#include "xenia/base/byte_order.h"
#include "xenia/base/math.h"
#include "xenia/base/memory.h"
namespace BE {
#include <beaengine/BeaEngine.h>
@@ -23,9 +24,11 @@ namespace cpu {
MMIOHandler* MMIOHandler::global_handler_ = nullptr;
// Implemented in the platform cc file.
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* mapping_base);
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* virtual_membase,
uint8_t* physical_membase);
std::unique_ptr<MMIOHandler> MMIOHandler::Install(uint8_t* mapping_base) {
std::unique_ptr<MMIOHandler> MMIOHandler::Install(uint8_t* virtual_membase,
uint8_t* physical_membase) {
// There can be only one handler at a time.
assert_null(global_handler_);
if (global_handler_) {
@@ -33,7 +36,7 @@ std::unique_ptr<MMIOHandler> MMIOHandler::Install(uint8_t* mapping_base) {
}
// Create the platform-specific handler.
auto handler = CreateMMIOHandler(mapping_base);
auto handler = CreateMMIOHandler(virtual_membase, physical_membase);
// Platform-specific initialization for the handler.
if (!handler->Initialize()) {
@@ -49,45 +52,50 @@ MMIOHandler::~MMIOHandler() {
global_handler_ = nullptr;
}
bool MMIOHandler::RegisterRange(uint64_t address, uint64_t mask, uint64_t size,
void* context, MMIOReadCallback read_callback,
bool MMIOHandler::RegisterRange(uint32_t virtual_address, uint32_t mask,
uint32_t size, void* context,
MMIOReadCallback read_callback,
MMIOWriteCallback write_callback) {
mapped_ranges_.push_back({
reinterpret_cast<uint64_t>(mapping_base_) | address,
0xFFFFFFFF00000000ull | mask, size, context, read_callback,
write_callback,
virtual_address, mask, size, context, read_callback, write_callback,
});
return true;
}
bool MMIOHandler::CheckLoad(uint64_t address, uint64_t* out_value) {
bool MMIOHandler::CheckLoad(uint32_t virtual_address, uint64_t* out_value) {
for (const auto& range : mapped_ranges_) {
if (((address | (uint64_t)mapping_base_) & range.mask) == range.address) {
*out_value = static_cast<uint32_t>(range.read(range.context, address));
if ((virtual_address & range.mask) == range.address) {
*out_value =
static_cast<uint32_t>(range.read(range.context, virtual_address));
return true;
}
}
return false;
}
bool MMIOHandler::CheckStore(uint64_t address, uint64_t value) {
bool MMIOHandler::CheckStore(uint32_t virtual_address, uint64_t value) {
for (const auto& range : mapped_ranges_) {
if (((address | (uint64_t)mapping_base_) & range.mask) == range.address) {
range.write(range.context, address, value);
if ((virtual_address & range.mask) == range.address) {
range.write(range.context, virtual_address, value);
return true;
}
}
return false;
}
uintptr_t MMIOHandler::AddWriteWatch(uint32_t guest_address, size_t length,
WriteWatchCallback callback,
void* callback_context,
void* callback_data) {
uintptr_t MMIOHandler::AddPhysicalWriteWatch(uint32_t guest_address,
size_t length,
WriteWatchCallback callback,
void* callback_context,
void* callback_data) {
uint32_t base_address = guest_address;
if (base_address > 0xA0000000) {
base_address -= 0xA0000000;
}
assert_true(base_address < 0x1FFFFFFF);
// Can only protect sizes matching system page size.
// This means we need to round up, which will cause spurious access
// violations and invalidations.
// TODO(benvanik): only invalidate if actually within the region?
length = xe::round_up(length, xe::page_size());
// Add to table. The slot reservation may evict a previous watch, which
// could include our target, so we do it first.
@@ -102,29 +110,29 @@ uintptr_t MMIOHandler::AddWriteWatch(uint32_t guest_address, size_t length,
write_watch_mutex_.unlock();
// Make the desired range read only under all address spaces.
auto host_address = mapping_base_ + base_address;
DWORD old_protect;
VirtualProtect(host_address, length, PAGE_READONLY, &old_protect);
VirtualProtect(host_address + 0xA0000000, length, PAGE_READONLY,
&old_protect);
VirtualProtect(host_address + 0xC0000000, length, PAGE_READONLY,
&old_protect);
VirtualProtect(host_address + 0xE0000000, length, PAGE_READONLY,
&old_protect);
VirtualProtect(physical_membase_ + entry->address, entry->length,
PAGE_READONLY, &old_protect);
VirtualProtect(virtual_membase_ + 0xA0000000 + entry->address, entry->length,
PAGE_READONLY, &old_protect);
VirtualProtect(virtual_membase_ + 0xC0000000 + entry->address, entry->length,
PAGE_READONLY, &old_protect);
VirtualProtect(virtual_membase_ + 0xE0000000 + entry->address, entry->length,
PAGE_READONLY, &old_protect);
return reinterpret_cast<uintptr_t>(entry);
}
void MMIOHandler::ClearWriteWatch(WriteWatchEntry* entry) {
auto host_address = mapping_base_ + entry->address;
DWORD old_protect;
VirtualProtect(host_address, entry->length, PAGE_READWRITE, &old_protect);
VirtualProtect(host_address + 0xA0000000, entry->length, PAGE_READWRITE,
&old_protect);
VirtualProtect(host_address + 0xC0000000, entry->length, PAGE_READWRITE,
&old_protect);
VirtualProtect(host_address + 0xE0000000, entry->length, PAGE_READWRITE,
&old_protect);
VirtualProtect(physical_membase_ + entry->address, entry->length,
PAGE_READWRITE, &old_protect);
VirtualProtect(virtual_membase_ + 0xA0000000 + entry->address, entry->length,
PAGE_READWRITE, &old_protect);
VirtualProtect(virtual_membase_ + 0xC0000000 + entry->address, entry->length,
PAGE_READWRITE, &old_protect);
VirtualProtect(virtual_membase_ + 0xE0000000 + entry->address, entry->length,
PAGE_READWRITE, &old_protect);
}
void MMIOHandler::CancelWriteWatch(uintptr_t watch_handle) {
@@ -145,17 +153,16 @@ void MMIOHandler::CancelWriteWatch(uintptr_t watch_handle) {
}
bool MMIOHandler::CheckWriteWatch(void* thread_state, uint64_t fault_address) {
uint32_t guest_address = uint32_t(fault_address - uintptr_t(mapping_base_));
uint32_t base_address = guest_address;
if (base_address > 0xA0000000) {
base_address -= 0xA0000000;
uint32_t physical_address = uint32_t(fault_address);
if (physical_address > 0x1FFFFFFF) {
physical_address &= 0x1FFFFFFF;
}
std::list<WriteWatchEntry*> pending_invalidates;
write_watch_mutex_.lock();
for (auto it = write_watches_.begin(); it != write_watches_.end();) {
auto entry = *it;
if (entry->address <= base_address &&
entry->address + entry->length > base_address) {
if (entry->address <= physical_address &&
entry->address + entry->length > physical_address) {
// Hit!
pending_invalidates.push_back(entry);
// TODO(benvanik): outside of lock?
@@ -176,7 +183,7 @@ bool MMIOHandler::CheckWriteWatch(void* thread_state, uint64_t fault_address) {
auto entry = pending_invalidates.back();
pending_invalidates.pop_back();
entry->callback(entry->callback_context, entry->callback_data,
guest_address);
physical_address);
delete entry;
}
// Range was watched, so lets eat this access violation.
@@ -185,18 +192,21 @@ bool MMIOHandler::CheckWriteWatch(void* thread_state, uint64_t fault_address) {
bool MMIOHandler::HandleAccessFault(void* thread_state,
uint64_t fault_address) {
if (fault_address < uint64_t(mapping_base_)) {
if (fault_address < uint64_t(virtual_membase_)) {
// Quick kill anything below our mapping base.
return false;
}
// Access violations are pretty rare, so we can do a linear search here.
// Only check if in the virtual range, as we only support virtual ranges.
const MMIORange* range = nullptr;
for (const auto& test_range : mapped_ranges_) {
if ((fault_address & test_range.mask) == test_range.address) {
// Address is within the range of this mapping.
range = &test_range;
break;
if (fault_address < uint64_t(physical_membase_)) {
for (const auto& test_range : mapped_ranges_) {
if ((uint32_t(fault_address) & test_range.mask) == test_range.address) {
// Address is within the range of this mapping.
range = &test_range;
break;
}
}
}
if (!range) {

View File

@@ -18,8 +18,8 @@
namespace xe {
namespace cpu {
typedef uint64_t (*MMIOReadCallback)(void* context, uint64_t addr);
typedef void (*MMIOWriteCallback)(void* context, uint64_t addr, uint64_t value);
typedef uint64_t (*MMIOReadCallback)(void* context, uint32_t addr);
typedef void (*MMIOWriteCallback)(void* context, uint32_t addr, uint64_t value);
typedef void (*WriteWatchCallback)(void* context_ptr, void* data_ptr,
uint32_t address);
@@ -29,19 +29,20 @@ class MMIOHandler {
public:
virtual ~MMIOHandler();
static std::unique_ptr<MMIOHandler> Install(uint8_t* mapping_base);
static std::unique_ptr<MMIOHandler> Install(uint8_t* virtual_membase,
uint8_t* physical_membase);
static MMIOHandler* global_handler() { return global_handler_; }
bool RegisterRange(uint64_t address, uint64_t mask, uint64_t size,
bool RegisterRange(uint32_t virtual_address, uint32_t mask, uint32_t size,
void* context, MMIOReadCallback read_callback,
MMIOWriteCallback write_callback);
bool CheckLoad(uint64_t address, uint64_t* out_value);
bool CheckStore(uint64_t address, uint64_t value);
bool CheckLoad(uint32_t virtual_address, uint64_t* out_value);
bool CheckStore(uint32_t virtual_address, uint64_t value);
uintptr_t AddWriteWatch(uint32_t guest_address, size_t length,
WriteWatchCallback callback, void* callback_context,
void* callback_data);
uintptr_t AddPhysicalWriteWatch(uint32_t guest_address, size_t length,
WriteWatchCallback callback,
void* callback_context, void* callback_data);
void CancelWriteWatch(uintptr_t watch_handle);
public:
@@ -56,7 +57,9 @@ class MMIOHandler {
void* callback_data;
};
MMIOHandler(uint8_t* mapping_base) : mapping_base_(mapping_base) {}
MMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase)
: virtual_membase_(virtual_membase),
physical_membase_(physical_membase) {}
virtual bool Initialize() = 0;
@@ -68,12 +71,13 @@ class MMIOHandler {
virtual uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
int32_t be_reg_index) = 0;
uint8_t* mapping_base_;
uint8_t* virtual_membase_;
uint8_t* physical_membase_;
struct MMIORange {
uint64_t address;
uint64_t mask;
uint64_t size;
uint32_t address;
uint32_t mask;
uint32_t size;
void* context;
MMIOReadCallback read;
MMIOWriteCallback write;

View File

@@ -11,6 +11,10 @@
#include <Windows.h>
namespace xe {
void CrashDump();
} // namespace xe
namespace xe {
namespace cpu {
@@ -18,7 +22,8 @@ LONG CALLBACK MMIOExceptionHandler(PEXCEPTION_POINTERS ex_info);
class WinMMIOHandler : public MMIOHandler {
public:
WinMMIOHandler(uint8_t* mapping_base) : MMIOHandler(mapping_base) {}
WinMMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase)
: MMIOHandler(virtual_membase, physical_membase) {}
~WinMMIOHandler() override;
protected:
@@ -30,8 +35,9 @@ class WinMMIOHandler : public MMIOHandler {
int32_t be_reg_index) override;
};
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* mapping_base) {
return std::make_unique<WinMMIOHandler>(mapping_base);
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* virtual_membase,
uint8_t* physical_membase) {
return std::make_unique<WinMMIOHandler>(virtual_membase, physical_membase);
}
bool WinMMIOHandler::Initialize() {
@@ -67,6 +73,7 @@ LONG CALLBACK MMIOExceptionHandler(PEXCEPTION_POINTERS ex_info) {
} else {
// Failed to handle; continue search for a handler (and die if no other
// handler is found).
xe::CrashDump();
return EXCEPTION_CONTINUE_SEARCH;
}
}

View File

@@ -148,10 +148,16 @@ bool Processor::Setup() {
backend_ = std::move(backend);
frontend_ = std::move(frontend);
interrupt_thread_state_ = new ThreadState(this, 0, 0, 128 * 1024, 0);
interrupt_thread_state_ =
new ThreadState(this, 0, ThreadStackType::kKernelStack, 0, 128 * 1024, 0);
interrupt_thread_state_->set_name("Interrupt");
interrupt_thread_block_ = memory_->SystemHeapAlloc(2048);
interrupt_thread_state_->context()->r[13] = interrupt_thread_block_;
XELOGI("Interrupt Thread %X Stack: %.8X-%.8X",
interrupt_thread_state_->thread_id(),
interrupt_thread_state_->stack_address(),
interrupt_thread_state_->stack_address() +
interrupt_thread_state_->stack_size());
return true;
}
@@ -325,16 +331,20 @@ bool Processor::Execute(ThreadState* thread_state, uint32_t address) {
PPCContext* context = thread_state->context();
// Setup registers.
uint64_t previous_lr = context->lr;
// Pad out stack a bit, as some games seem to overwrite the caller by about
// 16 to 32b.
context->r[1] -= 64 + 112;
// This could be set to anything to give us a unique identifier to track
// re-entrancy/etc.
uint64_t previous_lr = context->lr;
context->lr = 0xBEBEBEBE;
// Execute the function.
auto result = fn->Call(thread_state, uint32_t(context->lr));
context->lr = previous_lr;
context->r[1] += 64 + 112;
return result;
}

View File

@@ -30,8 +30,11 @@ bool RawModule::LoadFile(uint32_t base_address, const std::wstring& path) {
// Allocate memory.
// Since we have no real heap just load it wherever.
base_address_ = base_address;
memory_->LookupHeap(base_address_)
->AllocFixed(base_address_, file_length, 0,
kMemoryAllocationReserve | kMemoryAllocationCommit,
kMemoryProtectRead | kMemoryProtectWrite);
uint8_t* p = memory_->TranslateVirtual(base_address_);
std::memset(p, 0, file_length);
// Read into memory.
fread(p, file_length, 1, file);

View File

@@ -64,17 +64,15 @@ class TestFunction {
void Run(std::function<void(PPCContext*)> pre_call,
std::function<void(PPCContext*)> post_call) {
for (auto& processor : processors) {
memory->Zero(0, memory_size);
xe::cpu::Function* fn;
processor->ResolveFunction(0x1000, &fn);
uint32_t stack_size = 64 * 1024;
uint32_t stack_address = memory_size - stack_size;
uint32_t thread_state_address = stack_address - 0x1000;
auto thread_state =
std::make_unique<ThreadState>(processor.get(), 0x100, stack_address,
stack_size, thread_state_address);
auto thread_state = std::make_unique<ThreadState>(
processor.get(), 0x100, ThreadStackType::kUserStack, stack_address,
stack_size, thread_state_address);
auto ctx = thread_state->context();
ctx->lr = 0xBEBEBEBE;

View File

@@ -10,6 +10,7 @@
#include "xenia/cpu/thread_state.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/processor.h"
#include "xenia/debug/debugger.h"
@@ -26,15 +27,16 @@ using PPCContext = xe::cpu::frontend::PPCContext;
thread_local ThreadState* thread_state_ = nullptr;
ThreadState::ThreadState(Processor* processor, uint32_t thread_id,
uint32_t stack_address, uint32_t stack_size,
uint32_t thread_state_address)
ThreadStackType stack_type, uint32_t stack_address,
uint32_t stack_size, uint32_t pcr_address)
: processor_(processor),
memory_(processor->memory()),
thread_id_(thread_id),
stack_type_(stack_type),
name_(""),
backend_data_(0),
stack_size_(stack_size),
thread_state_address_(thread_state_address) {
pcr_address_(pcr_address) {
if (thread_id_ == UINT_MAX) {
// System thread. Assign the system thread ID with a high bit
// set so people know what's up.
@@ -43,22 +45,44 @@ ThreadState::ThreadState(Processor* processor, uint32_t thread_id,
}
backend_data_ = processor->backend()->AllocThreadData();
uint32_t stack_position;
if (!stack_address) {
// We must always allocate 64K as a guard region before stacks, as we can
// only Protect() on system page granularity.
stack_size = (stack_size + 0xFFF) & 0xFFFFF000;
uint32_t stack_alignment = (stack_size & 0xF000) ? 0x1000 : 0x10000;
uint32_t stack_padding = stack_alignment * 1;
uint32_t stack_padding = uint32_t(xe::page_size()); // Host page size.
uint32_t actual_stack_size = stack_padding + stack_size;
stack_address_ = memory()->SystemHeapAlloc(actual_stack_size, stack_alignment);
assert_true(!(stack_address & 0xFFF)); // just to be safe
stack_position = stack_address_ + actual_stack_size;
bool top_down;
switch (stack_type) {
case ThreadStackType::kKernelStack:
top_down = true;
break;
case ThreadStackType::kUserStack:
top_down = false;
break;
default:
assert_unhandled_case(stack_type);
break;
}
memory()
->LookupHeap(0x70000000)
->AllocRange(0x70000000, 0x7FFFFFFF, actual_stack_size, stack_alignment,
kMemoryAllocationReserve | kMemoryAllocationCommit,
kMemoryProtectRead | kMemoryProtectWrite, top_down,
&stack_address_);
assert_true(!(stack_address_ & 0xFFF)); // just to be safe
stack_allocated_ = true;
memset(memory()->TranslateVirtual(stack_address_), 0xBE, actual_stack_size);
memory()->Protect(stack_address_, stack_padding, X_PAGE_NOACCESS);
stack_base_ = stack_address_ + actual_stack_size;
stack_limit_ = stack_address_ + stack_padding;
memory()->Fill(stack_address_, actual_stack_size, 0xBE);
memory()
->LookupHeap(stack_address_)
->Protect(stack_address_, stack_padding, kMemoryProtectNoAccess);
} else {
stack_address_ = stack_address;
stack_position = stack_address_ + stack_size;
stack_allocated_ = false;
stack_base_ = stack_address_ + stack_size;
stack_limit_ = stack_address_;
}
assert_not_zero(stack_address_);
@@ -78,12 +102,8 @@ ThreadState::ThreadState(Processor* processor, uint32_t thread_id,
context_->thread_id = thread_id_;
// Set initial registers.
context_->r[1] = stack_position;
context_->r[13] = thread_state_address_;
// Pad out stack a bit, as some games seem to overwrite the caller by about
// 16 to 32b.
context_->r[1] -= 64;
context_->r[1] = stack_base_;
context_->r[13] = pcr_address_;
processor_->debugger()->OnThreadCreated(this);
}
@@ -100,7 +120,7 @@ ThreadState::~ThreadState() {
_aligned_free(context_);
if (stack_allocated_) {
memory()->SystemHeapFree(stack_address_);
memory()->LookupHeap(stack_address_)->Decommit(stack_address_, stack_size_);
}
}

View File

@@ -19,21 +19,30 @@ namespace cpu {
class Processor;
enum class ThreadStackType {
kKernelStack,
kUserStack,
};
class ThreadState {
public:
ThreadState(Processor* processor, uint32_t thread_id, uint32_t stack_address,
uint32_t stack_size, uint32_t thread_state_address);
ThreadState(Processor* processor, uint32_t thread_id,
ThreadStackType stack_type, uint32_t stack_address,
uint32_t stack_size, uint32_t pcr_address);
~ThreadState();
Processor* processor() const { return processor_; }
Memory* memory() const { return memory_; }
uint32_t thread_id() const { return thread_id_; }
ThreadStackType stack_type() const { return stack_type_; }
const std::string& name() const { return name_; }
void set_name(const std::string& value) { name_ = value; }
void* backend_data() const { return backend_data_; }
uint32_t stack_address() const { return stack_address_; }
uint32_t stack_size() const { return stack_size_; }
uint32_t thread_state_address() const { return thread_state_address_; }
uint32_t stack_base() const { return stack_base_; }
uint32_t stack_limit() const { return stack_limit_; }
uint32_t pcr_address() const { return pcr_address_; }
xe::cpu::frontend::PPCContext* context() const { return context_; }
bool Suspend() { return Suspend(~0); }
@@ -48,12 +57,15 @@ class ThreadState {
Processor* processor_;
Memory* memory_;
uint32_t thread_id_;
ThreadStackType stack_type_;
std::string name_;
void* backend_data_;
uint32_t stack_address_;
bool stack_allocated_;
uint32_t stack_size_;
uint32_t thread_state_address_;
uint32_t stack_base_;
uint32_t stack_limit_;
uint32_t pcr_address_;
// NOTE: must be 64b aligned for SSE ops.
xe::cpu::frontend::PPCContext* context_;

View File

@@ -18,21 +18,25 @@
#include "xenia/cpu/cpu-private.h"
#include "xenia/cpu/export_resolver.h"
#include "xenia/cpu/processor.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/objects/xmodule.h"
namespace xe {
namespace cpu {
using namespace xe::cpu;
using namespace xe::kernel;
using PPCContext = xe::cpu::frontend::PPCContext;
void UndefinedImport(PPCContext* ppc_state, void* arg0, void* arg1) {
XELOGE("call to undefined kernel import");
XELOGE("call to undefined import");
}
XexModule::XexModule(Processor* processor)
XexModule::XexModule(Processor* processor, KernelState* state)
: Module(processor),
processor_(processor),
kernel_state_(state),
xex_(nullptr),
base_address_(0),
low_address_(0),
@@ -104,8 +108,25 @@ bool XexModule::SetupLibraryImports(const xe_xex2_import_library_t* library) {
for (size_t n = 0; n < import_info_count; n++) {
const xe_xex2_import_info_t* info = &import_infos[n];
KernelExport* kernel_export =
export_resolver->GetExportByOrdinal(library->name, info->ordinal);
// Strip off the extension (for the symbol name)
std::string libname = library->name;
auto dot = libname.find_last_of('.');
if (dot != libname.npos) {
libname = libname.substr(0, dot);
}
KernelExport* kernel_export = NULL; // kernel export info
uint32_t user_export_addr = 0; // user export address
if (kernel_state_->IsKernelModule(library->name)) {
kernel_export =
export_resolver->GetExportByOrdinal(library->name, info->ordinal);
} else {
XModule* module = kernel_state_->GetModule(library->name);
if (module) {
user_export_addr = module->GetProcAddressByOrdinal(info->ordinal);
}
}
if (kernel_export) {
if (info->thunk_address) {
@@ -114,7 +135,7 @@ bool XexModule::SetupLibraryImports(const xe_xex2_import_library_t* library) {
snprintf(name, xe::countof(name), "%s", kernel_export->name);
}
} else {
snprintf(name, xe::countof(name), "__imp_%s_%.3X", library->name,
snprintf(name, xe::countof(name), "__imp_%s_%.3X", libname,
info->ordinal);
}
@@ -127,8 +148,8 @@ bool XexModule::SetupLibraryImports(const xe_xex2_import_library_t* library) {
var_info->set_status(SymbolInfo::STATUS_DEFINED);
// Grab, if available.
auto slot = memory_->TranslateVirtual<uint32_t*>(info->value_address);
if (kernel_export) {
auto slot = memory_->TranslateVirtual<uint32_t*>(info->value_address);
if (kernel_export->type == KernelExport::Function) {
// Not exactly sure what this should be...
if (info->thunk_address) {
@@ -151,53 +172,80 @@ bool XexModule::SetupLibraryImports(const xe_xex2_import_library_t* library) {
kernel_export->name);
}
}
} else if (user_export_addr) {
xe::store_and_swap<uint32_t>(slot, user_export_addr);
} else {
// No module found.
XELOGE("kernel import not found: %s", name);
if (info->thunk_address) {
*slot = xe::byte_swap(info->thunk_address);
} else {
*slot = xe::byte_swap(0xF00DF00D);
}
}
if (info->thunk_address) {
if (kernel_export) {
snprintf(name, xe::countof(name), "%s", kernel_export->name);
} else if (user_export_addr) {
snprintf(name, xe::countof(name), "__%s_%.3X", libname, info->ordinal);
} else {
snprintf(name, xe::countof(name), "__kernel_%s_%.3X", library->name,
snprintf(name, xe::countof(name), "__kernel_%s_%.3X", libname,
info->ordinal);
}
// On load we have something like this in memory:
// li r3, 0
// li r4, 0x1F5
// mtspr CTR, r11
// bctr
// Real consoles rewrite this with some code that sets r11.
// If we did that we'd still have to put a thunk somewhere and do the
// dynamic lookup. Instead, we rewrite it to use syscalls, as they
// aren't used on the 360. CPU backends can either take the syscall
// or do something smarter.
// sc
// blr
// nop
// nop
uint8_t* p = memory()->TranslateVirtual(info->thunk_address);
xe::store_and_swap<uint32_t>(p + 0x0, 0x44000002);
xe::store_and_swap<uint32_t>(p + 0x4, 0x4E800020);
xe::store_and_swap<uint32_t>(p + 0x8, 0x60000000);
xe::store_and_swap<uint32_t>(p + 0xC, 0x60000000);
if (user_export_addr) {
// Rewrite PPC code to set r11 to the target address
// So we'll have:
// lis r11, user_export_addr
// ori r11, r11, user_export_addr
// mtspr CTR, r11
// bctr
uint16_t hi_addr = (user_export_addr >> 16) & 0xFFFF;
uint16_t low_addr = user_export_addr & 0xFFFF;
FunctionInfo::ExternHandler handler = 0;
void* handler_data = 0;
if (kernel_export) {
handler =
(FunctionInfo::ExternHandler)kernel_export->function_data.shim;
handler_data = kernel_export->function_data.shim_data;
uint8_t* p = memory()->TranslateVirtual(info->thunk_address);
xe::store_and_swap<uint32_t>(p + 0x0, 0x3D600000 | hi_addr);
xe::store_and_swap<uint32_t>(p + 0x4, 0x616B0000 | low_addr);
} else {
handler = (FunctionInfo::ExternHandler)UndefinedImport;
handler_data = this;
}
// On load we have something like this in memory:
// li r3, 0
// li r4, 0x1F5
// mtspr CTR, r11
// bctr
// Real consoles rewrite this with some code that sets r11.
// If we did that we'd still have to put a thunk somewhere and do the
// dynamic lookup. Instead, we rewrite it to use syscalls, as they
// aren't used on the 360. CPU backends can either take the syscall
// or do something smarter.
// sc
// blr
// nop
// nop
uint8_t* p = memory()->TranslateVirtual(info->thunk_address);
xe::store_and_swap<uint32_t>(p + 0x0, 0x44000002);
xe::store_and_swap<uint32_t>(p + 0x4, 0x4E800020);
xe::store_and_swap<uint32_t>(p + 0x8, 0x60000000);
xe::store_and_swap<uint32_t>(p + 0xC, 0x60000000);
FunctionInfo* fn_info;
DeclareFunction(info->thunk_address, &fn_info);
fn_info->set_end_address(info->thunk_address + 16 - 4);
fn_info->set_name(name);
fn_info->SetupExtern(handler, handler_data, NULL);
fn_info->set_status(SymbolInfo::STATUS_DECLARED);
FunctionInfo::ExternHandler handler = 0;
void* handler_data = 0;
if (kernel_export) {
handler =
(FunctionInfo::ExternHandler)kernel_export->function_data.shim;
handler_data = kernel_export->function_data.shim_data;
} else {
handler = (FunctionInfo::ExternHandler)UndefinedImport;
handler_data = this;
}
FunctionInfo* fn_info;
DeclareFunction(info->thunk_address, &fn_info);
fn_info->set_end_address(info->thunk_address + 16 - 4);
fn_info->set_name(name);
fn_info->SetupExtern(handler, handler_data, NULL);
fn_info->set_status(SymbolInfo::STATUS_DECLARED);
}
}
}

View File

@@ -16,13 +16,17 @@
#include "xenia/kernel/util/xex2.h"
namespace xe {
// KernelState forward decl.
namespace kernel { class KernelState; }
namespace cpu {
class Runtime;
class XexModule : public xe::cpu::Module {
public:
XexModule(Processor* processor);
XexModule(Processor* processor, kernel::KernelState* state);
virtual ~XexModule();
xe_xex2_ref xex() const { return xex_; }
@@ -40,6 +44,7 @@ class XexModule : public xe::cpu::Module {
private:
Processor* processor_;
kernel::KernelState* kernel_state_;
std::string name_;
std::string path_;
xe_xex2_ref xex_;