Merge branch 'memory'
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -142,6 +142,7 @@ enum Opcode {
|
||||
OPCODE_STORE_CONTEXT,
|
||||
OPCODE_LOAD,
|
||||
OPCODE_STORE,
|
||||
OPCODE_MEMSET,
|
||||
OPCODE_PREFETCH,
|
||||
OPCODE_MAX,
|
||||
OPCODE_VECTOR_MAX,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
Reference in New Issue
Block a user