"Fix" debug console, we were checking the cvar before any cvars were loaded, and the condition it checks in AttachConsole is somehow always false
Remove dead #if 0'd code in math.h On amd64, page_size == 4096 constant, on amd64 w/ win32, allocation_granularity == 65536. These values for x86 windows havent changed over the last 20 years so this is probably safe and gives a modest code size reduction Enable XE_USE_KUSER_SHARED. This sources host time from KUSER_SHARED instead of from QueryPerformanceCounter, which is far faster, but only has a granularity of 100 nanoseconds. In some games seemingly random crashes were happening that were hard to trace because the faulting thread was actually not the one that was misbehaving, another threads stack was underflowing into the faulting thread. Added a bunch of code to synchronize the guest stack and host stack so that if a guest longjmps the host's stack will be adjusted. Changes were also made to allow the guest to call into a piece of an existing x64 function. This synchronization might have a slight performance impact on lower end cpus, to disable it set enable_host_guest_stack_synchronization to false. It is possible it may have introduced regressions, but i dont know of any yet So far, i know the synchronization change fixes the "hub crash" in super sonic and allows the game "london 2012" to go ingame. Removed emit_useless_fpscr_updates, not emitting these updates breaks the raiden game MapGuestAddressToMachineCode now returns nullptr if no address was found, instead of the start of the function add Processor::LookupModule Add Backend::DeinitializeBackendContext Use WriteRegisterRangeFromRing_WithKnownBound<0, 0xFFFF> in WriteRegisterRangeFromRing for inlining (previously regressed on performance of ExecutePacketType0) add notes about flags that trap in XamInputGetCapabilities 0 == 3 in XamInputGetCapabilities Name arg 2 of XamInputSetState PrefetchW in critical section kernel funcs if available & doing cmpxchg Add terminated field to X_KTHREAD, set it on termination Expanded the logic of NtResumeThread/NtSuspendThread to include checking the type of the handle (in release, LookupObject doesnt seem to do anything with the type) and returning X_STATUS_OBJECT_TYPE_MISMATCH if invalid. Do termination check in NtSuspendThread. Add basic host exception messagebox, need to flesh it out more (maybe use the new stack tracking stuff if on guest thrd?) Add rdrand patching hack, mostly affects users with nvidia cards who have many threads on zen Use page_size_shift in more places Once again disable precompilation! Raiden is mostly weird ppc asm which probably breaks the precompilation. The code is still useful for running the compiler over the whole of an xex in debug to test for issues "Fix" debug console, we were checking the cvar before any cvars were loaded, and the condition it checks in AttachConsole is somehow always false Remove dead #if 0'd code in math.h On amd64, page_size == 4096 constant, on amd64 w/ win32, allocation_granularity == 65536. These values for x86 windows havent changed over the last 20 years so this is probably safe and gives a modest code size reduction Enable XE_USE_KUSER_SHARED. This sources host time from KUSER_SHARED instead of from QueryPerformanceCounter, which is far faster, but only has a granularity of 100 nanoseconds. In some games seemingly random crashes were happening that were hard to trace because the faulting thread was actually not the one that was misbehaving, another threads stack was underflowing into the faulting thread. Added a bunch of code to synchronize the guest stack and host stack so that if a guest longjmps the host's stack will be adjusted. Changes were also made to allow the guest to call into a piece of an existing x64 function. This synchronization might have a slight performance impact on lower end cpus, to disable it set enable_host_guest_stack_synchronization to false. It is possible it may have introduced regressions, but i dont know of any yet So far, i know the synchronization change fixes the "hub crash" in super sonic and allows the game "london 2012" to go ingame. Removed emit_useless_fpscr_updates, not emitting these updates breaks the raiden game MapGuestAddressToMachineCode now returns nullptr if no address was found, instead of the start of the function add Processor::LookupModule Add Backend::DeinitializeBackendContext Use WriteRegisterRangeFromRing_WithKnownBound<0, 0xFFFF> in WriteRegisterRangeFromRing for inlining (previously regressed on performance of ExecutePacketType0) add notes about flags that trap in XamInputGetCapabilities 0 == 3 in XamInputGetCapabilities Name arg 2 of XamInputSetState PrefetchW in critical section kernel funcs if available & doing cmpxchg Add terminated field to X_KTHREAD, set it on termination Expanded the logic of NtResumeThread/NtSuspendThread to include checking the type of the handle (in release, LookupObject doesnt seem to do anything with the type) and returning X_STATUS_OBJECT_TYPE_MISMATCH if invalid. Do termination check in NtSuspendThread. Add basic host exception messagebox, need to flesh it out more (maybe use the new stack tracking stuff if on guest thrd?) Add rdrand patching hack, mostly affects users with nvidia cards who have many threads on zen Use page_size_shift in more places Once again disable precompilation! Raiden is mostly weird ppc asm which probably breaks the precompilation. The code is still useful for running the compiler over the whole of an xex in debug to test for issues
This commit is contained in:
@@ -67,7 +67,22 @@ class Backend {
|
||||
// up until the start of ctx may be used by the backend to store whatever data
|
||||
// they want
|
||||
virtual void InitializeBackendContext(void* ctx) {}
|
||||
|
||||
/*
|
||||
Free any dynamically allocated data/resources that the backendcontext uses
|
||||
*/
|
||||
virtual void DeinitializeBackendContext(void* ctx) {}
|
||||
virtual void SetGuestRoundingMode(void* ctx, unsigned int mode){};
|
||||
/*
|
||||
called by KeSetCurrentStackPointers in xboxkrnl_threading.cc just prior
|
||||
to calling XThread::Reenter this is an opportunity for a backend to clear any
|
||||
data related to the guest stack
|
||||
|
||||
in the case of the X64 backend, it means we reset the stackpoint index
|
||||
to 0, since its a new stack and all of our old entries are invalid now
|
||||
|
||||
* */
|
||||
virtual void PrepareForReentry(void* ctx) {}
|
||||
|
||||
protected:
|
||||
Processor* processor_ = nullptr;
|
||||
|
||||
@@ -31,7 +31,16 @@ DEFINE_bool(record_mmio_access_exceptions, true,
|
||||
"For guest addresses records whether we caught any mmio accesses "
|
||||
"for them. This info can then be used on a subsequent run to "
|
||||
"instruct the recompiler to emit checks",
|
||||
"CPU");
|
||||
"x64");
|
||||
|
||||
DEFINE_int64(max_stackpoints, 65536,
|
||||
"Max number of host->guest stack mappings we can record.", "x64");
|
||||
|
||||
DEFINE_bool(enable_host_guest_stack_synchronization, true,
|
||||
"Records entries for guest/host stack mappings at function starts "
|
||||
"and checks for reentry at return sites. Has slight performance "
|
||||
"impact, but fixes crashes in games that use setjmp/longjmp.",
|
||||
"x64");
|
||||
#if XE_X64_PROFILER_AVAILABLE == 1
|
||||
DECLARE_bool(instrument_call_times);
|
||||
#endif
|
||||
@@ -41,15 +50,29 @@ namespace cpu {
|
||||
namespace backend {
|
||||
namespace x64 {
|
||||
|
||||
class X64ThunkEmitter : public X64Emitter {
|
||||
class X64HelperEmitter : public X64Emitter {
|
||||
public:
|
||||
X64ThunkEmitter(X64Backend* backend, XbyakAllocator* allocator);
|
||||
~X64ThunkEmitter() override;
|
||||
struct _code_offsets {
|
||||
size_t prolog;
|
||||
size_t prolog_stack_alloc;
|
||||
size_t body;
|
||||
size_t epilog;
|
||||
size_t tail;
|
||||
};
|
||||
X64HelperEmitter(X64Backend* backend, XbyakAllocator* allocator);
|
||||
~X64HelperEmitter() override;
|
||||
HostToGuestThunk EmitHostToGuestThunk();
|
||||
GuestToHostThunk EmitGuestToHostThunk();
|
||||
ResolveFunctionThunk EmitResolveFunctionThunk();
|
||||
void* EmitGuestAndHostSynchronizeStackHelper();
|
||||
// 1 for loading byte, 2 for halfword and 4 for word.
|
||||
// these specialized versions save space in the caller
|
||||
void* EmitGuestAndHostSynchronizeStackSizeLoadThunk(
|
||||
void* sync_func, unsigned stack_element_size);
|
||||
|
||||
private:
|
||||
void* EmitCurrentForOffsets(const _code_offsets& offsets,
|
||||
size_t stack_size = 0);
|
||||
// The following four functions provide save/load functionality for registers.
|
||||
// They assume at least StackLayout::THUNK_STACK_SIZE bytes have been
|
||||
// allocated on the stack.
|
||||
@@ -184,11 +207,26 @@ bool X64Backend::Initialize(Processor* processor) {
|
||||
|
||||
// Generate thunks used to transition between jitted code and host code.
|
||||
XbyakAllocator allocator;
|
||||
X64ThunkEmitter thunk_emitter(this, &allocator);
|
||||
X64HelperEmitter thunk_emitter(this, &allocator);
|
||||
host_to_guest_thunk_ = thunk_emitter.EmitHostToGuestThunk();
|
||||
guest_to_host_thunk_ = thunk_emitter.EmitGuestToHostThunk();
|
||||
resolve_function_thunk_ = thunk_emitter.EmitResolveFunctionThunk();
|
||||
|
||||
if (cvars::enable_host_guest_stack_synchronization) {
|
||||
synchronize_guest_and_host_stack_helper_ =
|
||||
thunk_emitter.EmitGuestAndHostSynchronizeStackHelper();
|
||||
|
||||
synchronize_guest_and_host_stack_helper_size8_ =
|
||||
thunk_emitter.EmitGuestAndHostSynchronizeStackSizeLoadThunk(
|
||||
synchronize_guest_and_host_stack_helper_, 1);
|
||||
synchronize_guest_and_host_stack_helper_size16_ =
|
||||
thunk_emitter.EmitGuestAndHostSynchronizeStackSizeLoadThunk(
|
||||
synchronize_guest_and_host_stack_helper_, 2);
|
||||
synchronize_guest_and_host_stack_helper_size32_ =
|
||||
thunk_emitter.EmitGuestAndHostSynchronizeStackSizeLoadThunk(
|
||||
synchronize_guest_and_host_stack_helper_, 4);
|
||||
}
|
||||
|
||||
// Set the code cache to use the ResolveFunction thunk for default
|
||||
// indirections.
|
||||
assert_zero(uint64_t(resolve_function_thunk_) & 0xFFFFFFFF00000000ull);
|
||||
@@ -203,9 +241,10 @@ bool X64Backend::Initialize(Processor* processor) {
|
||||
|
||||
// Setup exception callback
|
||||
ExceptionHandler::Install(&ExceptionCallbackThunk, this);
|
||||
|
||||
processor->memory()->SetMMIOExceptionRecordingCallback(
|
||||
ForwardMMIOAccessForRecording, (void*)this);
|
||||
if (cvars::record_mmio_access_exceptions) {
|
||||
processor->memory()->SetMMIOExceptionRecordingCallback(
|
||||
ForwardMMIOAccessForRecording, (void*)this);
|
||||
}
|
||||
|
||||
#if XE_X64_PROFILER_AVAILABLE == 1
|
||||
if (cvars::instrument_call_times) {
|
||||
@@ -509,23 +548,32 @@ bool X64Backend::ExceptionCallback(Exception* ex) {
|
||||
return processor()->OnThreadBreakpointHit(ex);
|
||||
}
|
||||
|
||||
X64ThunkEmitter::X64ThunkEmitter(X64Backend* backend, XbyakAllocator* allocator)
|
||||
X64HelperEmitter::X64HelperEmitter(X64Backend* backend,
|
||||
XbyakAllocator* allocator)
|
||||
: X64Emitter(backend, allocator) {}
|
||||
|
||||
X64ThunkEmitter::~X64ThunkEmitter() {}
|
||||
X64HelperEmitter::~X64HelperEmitter() {}
|
||||
void* X64HelperEmitter::EmitCurrentForOffsets(const _code_offsets& code_offsets,
|
||||
size_t stack_size) {
|
||||
EmitFunctionInfo func_info = {};
|
||||
func_info.code_size.total = getSize();
|
||||
func_info.code_size.prolog = code_offsets.body - code_offsets.prolog;
|
||||
func_info.code_size.body = code_offsets.epilog - code_offsets.body;
|
||||
func_info.code_size.epilog = code_offsets.tail - code_offsets.epilog;
|
||||
func_info.code_size.tail = getSize() - code_offsets.tail;
|
||||
func_info.prolog_stack_alloc_offset =
|
||||
code_offsets.prolog_stack_alloc - code_offsets.prolog;
|
||||
func_info.stack_size = stack_size;
|
||||
|
||||
HostToGuestThunk X64ThunkEmitter::EmitHostToGuestThunk() {
|
||||
void* fn = Emplace(func_info);
|
||||
return fn;
|
||||
}
|
||||
HostToGuestThunk X64HelperEmitter::EmitHostToGuestThunk() {
|
||||
// rcx = target
|
||||
// rdx = arg0 (context)
|
||||
// r8 = arg1 (guest return address)
|
||||
|
||||
struct _code_offsets {
|
||||
size_t prolog;
|
||||
size_t prolog_stack_alloc;
|
||||
size_t body;
|
||||
size_t epilog;
|
||||
size_t tail;
|
||||
} code_offsets = {};
|
||||
_code_offsets code_offsets = {};
|
||||
|
||||
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
|
||||
|
||||
@@ -576,19 +624,13 @@ HostToGuestThunk X64ThunkEmitter::EmitHostToGuestThunk() {
|
||||
return (HostToGuestThunk)fn;
|
||||
}
|
||||
|
||||
GuestToHostThunk X64ThunkEmitter::EmitGuestToHostThunk() {
|
||||
GuestToHostThunk X64HelperEmitter::EmitGuestToHostThunk() {
|
||||
// rcx = target function
|
||||
// rdx = arg0
|
||||
// r8 = arg1
|
||||
// r9 = arg2
|
||||
|
||||
struct _code_offsets {
|
||||
size_t prolog;
|
||||
size_t prolog_stack_alloc;
|
||||
size_t body;
|
||||
size_t epilog;
|
||||
size_t tail;
|
||||
} code_offsets = {};
|
||||
_code_offsets code_offsets = {};
|
||||
|
||||
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
|
||||
|
||||
@@ -635,17 +677,11 @@ GuestToHostThunk X64ThunkEmitter::EmitGuestToHostThunk() {
|
||||
// X64Emitter handles actually resolving functions.
|
||||
uint64_t ResolveFunction(void* raw_context, uint64_t target_address);
|
||||
|
||||
ResolveFunctionThunk X64ThunkEmitter::EmitResolveFunctionThunk() {
|
||||
ResolveFunctionThunk X64HelperEmitter::EmitResolveFunctionThunk() {
|
||||
// ebx = target PPC address
|
||||
// rcx = context
|
||||
|
||||
struct _code_offsets {
|
||||
size_t prolog;
|
||||
size_t prolog_stack_alloc;
|
||||
size_t body;
|
||||
size_t epilog;
|
||||
size_t tail;
|
||||
} code_offsets = {};
|
||||
_code_offsets code_offsets = {};
|
||||
|
||||
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
|
||||
|
||||
@@ -688,8 +724,116 @@ ResolveFunctionThunk X64ThunkEmitter::EmitResolveFunctionThunk() {
|
||||
void* fn = Emplace(func_info);
|
||||
return (ResolveFunctionThunk)fn;
|
||||
}
|
||||
// r11 = size of callers stack, r8 = return address w/ adjustment
|
||||
//i'm not proud of this code, but it shouldn't be executed frequently at all
|
||||
void* X64HelperEmitter::EmitGuestAndHostSynchronizeStackHelper() {
|
||||
_code_offsets code_offsets = {};
|
||||
code_offsets.prolog = getSize();
|
||||
mov(rbx, GetBackendCtxPtr(offsetof(X64BackendContext, stackpoints)));
|
||||
mov(eax,
|
||||
GetBackendCtxPtr(offsetof(X64BackendContext, current_stackpoint_depth)));
|
||||
|
||||
void X64ThunkEmitter::EmitSaveVolatileRegs() {
|
||||
lea(ecx, ptr[eax - 1]);
|
||||
mov(r9d, ptr[GetContextReg() + offsetof(ppc::PPCContext, r[1])]);
|
||||
|
||||
Xbyak::Label looper{};
|
||||
Xbyak::Label loopout{};
|
||||
Xbyak::Label signed_underflow{};
|
||||
xor_(r12d, r12d);
|
||||
|
||||
//todo: should use Loop instruction here if hasFastLoop,
|
||||
//currently xbyak does not support it but its super easy to modify xbyak to have it
|
||||
L(looper);
|
||||
imul(edx, ecx, sizeof(X64BackendStackpoint));
|
||||
mov(r10d, ptr[rbx + rdx + offsetof(X64BackendStackpoint, guest_stack_)]);
|
||||
|
||||
cmp(r10d, r9d);
|
||||
|
||||
jge(loopout, T_NEAR);
|
||||
|
||||
inc(r12d);
|
||||
|
||||
if (IsFeatureEnabled(kX64FlagsIndependentVars)) {
|
||||
dec(ecx);
|
||||
} else {
|
||||
sub(ecx, 1);
|
||||
}
|
||||
js(signed_underflow, T_NEAR); // should be impossible!!
|
||||
|
||||
|
||||
jmp(looper, T_NEAR);
|
||||
L(loopout);
|
||||
Xbyak::Label skip_adjust{};
|
||||
cmp(r12d, 1);//should never happen?
|
||||
jle(skip_adjust, T_NEAR);
|
||||
mov(rsp, ptr[rbx + rdx + offsetof(X64BackendStackpoint, host_stack_)]);
|
||||
if (IsFeatureEnabled(kX64FlagsIndependentVars)) {
|
||||
inc(ecx);
|
||||
} else {
|
||||
add(ecx, 1);
|
||||
}
|
||||
|
||||
// this->DebugBreak();
|
||||
sub(rsp, r11); // adjust stack
|
||||
|
||||
mov(GetBackendCtxPtr(offsetof(X64BackendContext, current_stackpoint_depth)),
|
||||
ecx); // set next stackpoint index to be after the one we restored to
|
||||
L(skip_adjust);
|
||||
|
||||
jmp(r8); // return to caller
|
||||
code_offsets.prolog_stack_alloc = getSize();
|
||||
code_offsets.body = getSize();
|
||||
code_offsets.epilog = getSize();
|
||||
code_offsets.tail = getSize();
|
||||
|
||||
L(signed_underflow);
|
||||
//find a good, compact way to signal error here
|
||||
// maybe an invalid opcode that we execute, then detect in an exception handler?
|
||||
|
||||
this->DebugBreak();
|
||||
// stack unwinding, take first entry
|
||||
//actually, no reason to have this
|
||||
|
||||
/*mov(rsp, ptr[rbx + offsetof(X64BackendStackpoint, host_stack_)]);
|
||||
mov(ptr[rbx + offsetof(X64BackendStackpoint, guest_stack_)], r9d);
|
||||
sub(rsp, r11);
|
||||
xor_(eax, eax);
|
||||
inc(eax);
|
||||
mov(GetBackendCtxPtr(offsetof(X64BackendContext, current_stackpoint_depth)),
|
||||
eax);
|
||||
|
||||
jmp(r8);*/
|
||||
// this->DebugBreak(); // err, add an xe::FatalError to call for this
|
||||
|
||||
return EmitCurrentForOffsets(code_offsets);
|
||||
}
|
||||
|
||||
void* X64HelperEmitter::EmitGuestAndHostSynchronizeStackSizeLoadThunk(
|
||||
void* sync_func, unsigned stack_element_size) {
|
||||
_code_offsets code_offsets = {};
|
||||
code_offsets.prolog = getSize();
|
||||
pop(r8); // return address
|
||||
|
||||
switch (stack_element_size) {
|
||||
case 4:
|
||||
mov(r11d, ptr[r8]);
|
||||
break;
|
||||
case 2:
|
||||
movzx(r11d, word[r8]);
|
||||
break;
|
||||
case 1:
|
||||
movzx(r11d, byte[r8]);
|
||||
break;
|
||||
}
|
||||
add(r8, stack_element_size);
|
||||
jmp(sync_func, T_NEAR);
|
||||
code_offsets.prolog_stack_alloc = getSize();
|
||||
code_offsets.body = getSize();
|
||||
code_offsets.epilog = getSize();
|
||||
code_offsets.tail = getSize();
|
||||
return EmitCurrentForOffsets(code_offsets);
|
||||
}
|
||||
void X64HelperEmitter::EmitSaveVolatileRegs() {
|
||||
// Save off volatile registers.
|
||||
// mov(qword[rsp + offsetof(StackLayout::Thunk, r[0])], rax);
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[1])], rcx);
|
||||
@@ -711,7 +855,7 @@ void X64ThunkEmitter::EmitSaveVolatileRegs() {
|
||||
vmovaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[5])], xmm5);
|
||||
}
|
||||
|
||||
void X64ThunkEmitter::EmitLoadVolatileRegs() {
|
||||
void X64HelperEmitter::EmitLoadVolatileRegs() {
|
||||
// mov(rax, qword[rsp + offsetof(StackLayout::Thunk, r[0])]);
|
||||
mov(rcx, qword[rsp + offsetof(StackLayout::Thunk, r[1])]);
|
||||
mov(rdx, qword[rsp + offsetof(StackLayout::Thunk, r[2])]);
|
||||
@@ -732,7 +876,7 @@ void X64ThunkEmitter::EmitLoadVolatileRegs() {
|
||||
vmovaps(xmm5, qword[rsp + offsetof(StackLayout::Thunk, xmm[5])]);
|
||||
}
|
||||
|
||||
void X64ThunkEmitter::EmitSaveNonvolatileRegs() {
|
||||
void X64HelperEmitter::EmitSaveNonvolatileRegs() {
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[0])], rbx);
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[1])], rbp);
|
||||
#if XE_PLATFORM_WIN32
|
||||
@@ -760,7 +904,7 @@ void X64ThunkEmitter::EmitSaveNonvolatileRegs() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void X64ThunkEmitter::EmitLoadNonvolatileRegs() {
|
||||
void X64HelperEmitter::EmitLoadNonvolatileRegs() {
|
||||
mov(rbx, qword[rsp + offsetof(StackLayout::Thunk, r[0])]);
|
||||
mov(rbp, qword[rsp + offsetof(StackLayout::Thunk, r[1])]);
|
||||
#if XE_PLATFORM_WIN32
|
||||
@@ -788,16 +932,41 @@ void X64ThunkEmitter::EmitLoadNonvolatileRegs() {
|
||||
}
|
||||
void X64Backend::InitializeBackendContext(void* ctx) {
|
||||
X64BackendContext* bctx = BackendContextForGuestContext(ctx);
|
||||
bctx->ResolveFunction_Ptr = reinterpret_cast<void*>(&ResolveFunction);
|
||||
bctx->mxcsr_fpu =
|
||||
DEFAULT_FPU_MXCSR; // idk if this is right, check on rgh what the
|
||||
// rounding on ppc is at startup
|
||||
|
||||
/*
|
||||
todo: stackpoint arrays should be pooled virtual memory at the very
|
||||
least there may be some fancy virtual address tricks we can do here
|
||||
|
||||
*/
|
||||
|
||||
bctx->stackpoints = cvars::enable_host_guest_stack_synchronization
|
||||
? new X64BackendStackpoint[cvars::max_stackpoints]
|
||||
: nullptr;
|
||||
bctx->current_stackpoint_depth = 0;
|
||||
bctx->mxcsr_vmx = DEFAULT_VMX_MXCSR;
|
||||
bctx->flags = 0;
|
||||
// https://media.discordapp.net/attachments/440280035056943104/1000765256643125308/unknown.png
|
||||
bctx->Ox1000 = 0x1000;
|
||||
bctx->guest_tick_count = Clock::GetGuestTickCountPointer();
|
||||
}
|
||||
void X64Backend::DeinitializeBackendContext(void* ctx) {
|
||||
X64BackendContext* bctx = BackendContextForGuestContext(ctx);
|
||||
|
||||
if (bctx->stackpoints) {
|
||||
delete[] bctx->stackpoints;
|
||||
bctx->stackpoints = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void X64Backend::PrepareForReentry(void* ctx) {
|
||||
X64BackendContext* bctx = BackendContextForGuestContext(ctx);
|
||||
|
||||
bctx->current_stackpoint_depth = 0;
|
||||
}
|
||||
|
||||
const uint32_t mxcsr_table[8] = {
|
||||
0x1F80, 0x7F80, 0x5F80, 0x3F80, 0x9F80, 0xFF80, 0xDF80, 0xBF80,
|
||||
};
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
#endif
|
||||
|
||||
DECLARE_int64(x64_extension_mask);
|
||||
|
||||
DECLARE_int64(max_stackpoints);
|
||||
DECLARE_bool(enable_host_guest_stack_synchronization);
|
||||
namespace xe {
|
||||
class Exception;
|
||||
} // namespace xe
|
||||
@@ -41,14 +42,25 @@ typedef void* (*HostToGuestThunk)(void* target, void* arg0, void* arg1);
|
||||
typedef void* (*GuestToHostThunk)(void* target, void* arg0, void* arg1);
|
||||
typedef void (*ResolveFunctionThunk)();
|
||||
|
||||
struct X64BackendStackpoint {
|
||||
uint64_t host_stack_;
|
||||
unsigned guest_stack_;
|
||||
// pad to 16 bytes so we never end up having a 64 bit load/store for
|
||||
// host_stack_ straddling two lines. Consider this field reserved for future
|
||||
// use
|
||||
unsigned unused_;
|
||||
};
|
||||
// located prior to the ctx register
|
||||
// some things it would be nice to have be per-emulator instance instead of per
|
||||
// context (somehow placing a global X64BackendCtx prior to membase, so we can
|
||||
// negatively index the membase reg)
|
||||
struct X64BackendContext {
|
||||
void* ResolveFunction_Ptr; // cached pointer to resolvefunction
|
||||
// guest_tick_count is used if inline_loadclock is used
|
||||
uint64_t* guest_tick_count;
|
||||
// records mapping of host_stack to guest_stack
|
||||
X64BackendStackpoint* stackpoints;
|
||||
|
||||
unsigned int current_stackpoint_depth;
|
||||
unsigned int mxcsr_fpu; // currently, the way we implement rounding mode
|
||||
// affects both vmx and the fpu
|
||||
unsigned int mxcsr_vmx;
|
||||
@@ -81,6 +93,19 @@ class X64Backend : public Backend {
|
||||
return resolve_function_thunk_;
|
||||
}
|
||||
|
||||
void* synchronize_guest_and_host_stack_helper() const {
|
||||
return synchronize_guest_and_host_stack_helper_;
|
||||
}
|
||||
void* synchronize_guest_and_host_stack_helper_for_size(size_t sz) const {
|
||||
switch (sz) {
|
||||
case 1:
|
||||
return synchronize_guest_and_host_stack_helper_size8_;
|
||||
case 2:
|
||||
return synchronize_guest_and_host_stack_helper_size16_;
|
||||
default:
|
||||
return synchronize_guest_and_host_stack_helper_size32_;
|
||||
}
|
||||
}
|
||||
bool Initialize(Processor* processor) override;
|
||||
|
||||
void CommitExecutableRange(uint32_t guest_low, uint32_t guest_high) override;
|
||||
@@ -97,7 +122,8 @@ class X64Backend : public Backend {
|
||||
void InstallBreakpoint(Breakpoint* breakpoint, Function* fn) override;
|
||||
void UninstallBreakpoint(Breakpoint* breakpoint) override;
|
||||
virtual void InitializeBackendContext(void* ctx) override;
|
||||
|
||||
virtual void DeinitializeBackendContext(void* ctx) override;
|
||||
virtual void PrepareForReentry(void* ctx) override;
|
||||
X64BackendContext* BackendContextForGuestContext(void* ctx) {
|
||||
return reinterpret_cast<X64BackendContext*>(
|
||||
reinterpret_cast<intptr_t>(ctx) - sizeof(X64BackendContext));
|
||||
@@ -120,7 +146,12 @@ class X64Backend : public Backend {
|
||||
HostToGuestThunk host_to_guest_thunk_;
|
||||
GuestToHostThunk guest_to_host_thunk_;
|
||||
ResolveFunctionThunk resolve_function_thunk_;
|
||||
void* synchronize_guest_and_host_stack_helper_ = nullptr;
|
||||
|
||||
// loads stack sizes 1 byte, 2 bytes or 4 bytes
|
||||
void* synchronize_guest_and_host_stack_helper_size8_ = nullptr;
|
||||
void* synchronize_guest_and_host_stack_helper_size16_ = nullptr;
|
||||
void* synchronize_guest_and_host_stack_helper_size32_ = nullptr;
|
||||
#if XE_X64_PROFILER_AVAILABLE == 1
|
||||
GuestProfilerData profiler_data_;
|
||||
#endif
|
||||
|
||||
@@ -213,6 +213,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, EmitFunctionInfo& func_info) {
|
||||
func_info.stack_size = stack_size;
|
||||
stack_size_ = stack_size;
|
||||
|
||||
PushStackpoint();
|
||||
sub(rsp, (uint32_t)stack_size);
|
||||
|
||||
code_offsets.prolog_stack_alloc = getSize();
|
||||
@@ -271,6 +272,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, EmitFunctionInfo& func_info) {
|
||||
*/
|
||||
// Body.
|
||||
auto block = builder->first_block();
|
||||
synchronize_stack_on_next_instruction_ = false;
|
||||
while (block) {
|
||||
ForgetMxcsrMode(); // at start of block, mxcsr mode is undefined
|
||||
|
||||
@@ -287,6 +289,12 @@ bool X64Emitter::Emit(HIRBuilder* builder, EmitFunctionInfo& func_info) {
|
||||
// Process instructions.
|
||||
const Instr* instr = block->instr_head;
|
||||
while (instr) {
|
||||
if (synchronize_stack_on_next_instruction_) {
|
||||
if (instr->GetOpcodeNum() != hir::OPCODE_SOURCE_OFFSET) {
|
||||
synchronize_stack_on_next_instruction_ = false;
|
||||
EnsureSynchronizedGuestAndHostStack();
|
||||
}
|
||||
}
|
||||
const Instr* new_tail = instr;
|
||||
if (!SelectSequence(this, instr, &new_tail)) {
|
||||
// No sequence found!
|
||||
@@ -314,6 +322,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, EmitFunctionInfo& func_info) {
|
||||
EmitProfilerEpilogue();
|
||||
|
||||
add(rsp, (uint32_t)stack_size);
|
||||
PopStackpoint();
|
||||
ret();
|
||||
// todo: do some kind of sorting by alignment?
|
||||
for (auto&& tail_item : tail_code_) {
|
||||
@@ -453,12 +462,186 @@ void X64Emitter::UnimplementedInstr(const hir::Instr* i) {
|
||||
|
||||
// This is used by the X64ThunkEmitter's ResolveFunctionThunk.
|
||||
uint64_t ResolveFunction(void* raw_context, uint64_t target_address) {
|
||||
auto thread_state =
|
||||
reinterpret_cast<ppc::PPCContext_s*>(raw_context)->thread_state;
|
||||
auto guest_context = reinterpret_cast<ppc::PPCContext_s*>(raw_context);
|
||||
|
||||
auto thread_state = guest_context->thread_state;
|
||||
|
||||
// TODO(benvanik): required?
|
||||
assert_not_zero(target_address);
|
||||
|
||||
/*
|
||||
todo: refactor this!
|
||||
|
||||
The purpose of this code is to allow guest longjmp to call into
|
||||
the body of an existing host function. There are a lot of conditions we
|
||||
have to check here to ensure that we do not mess up a normal call to a
|
||||
function
|
||||
|
||||
The address must be within an XexModule (may need to make some changes
|
||||
to instructionaddressflags to remove this limitation) The target address
|
||||
must be a known return site. The guest address must be part of a function
|
||||
that was already translated.
|
||||
|
||||
*/
|
||||
|
||||
if (cvars::enable_host_guest_stack_synchronization) {
|
||||
auto processor = thread_state->processor();
|
||||
auto module_for_address =
|
||||
processor->LookupModule(static_cast<uint32_t>(target_address));
|
||||
|
||||
if (module_for_address) {
|
||||
XexModule* xexmod = dynamic_cast<XexModule*>(module_for_address);
|
||||
if (xexmod) {
|
||||
InfoCacheFlags* flags = xexmod->GetInstructionAddressFlags(
|
||||
static_cast<uint32_t>(target_address));
|
||||
if (flags) {
|
||||
if (flags->is_return_site) {
|
||||
auto ones_with_address = processor->FindFunctionsWithAddress(
|
||||
static_cast<uint32_t>(target_address));
|
||||
if (ones_with_address.size() != 0) {
|
||||
// this loop to find a host address for the guest address is
|
||||
// necessary because FindFunctionsWithAddress works via a range
|
||||
// check, but if the function consists of multiple blocks
|
||||
// scattered around with "holes" of instructions that cannot be
|
||||
// reached in between those holes the instructions that cannot be
|
||||
// reached will incorrectly be considered members of the function
|
||||
|
||||
X64Function* candidate = nullptr;
|
||||
uintptr_t host_address = 0;
|
||||
for (auto&& entry : ones_with_address) {
|
||||
X64Function* xfunc = static_cast<X64Function*>(entry);
|
||||
|
||||
host_address = xfunc->MapGuestAddressToMachineCode(
|
||||
static_cast<uint32_t>(target_address));
|
||||
// host address does exist within the function, and that host
|
||||
// function is not the start of the function, it is instead
|
||||
// somewhere within its existing body
|
||||
// i originally did not have this (xfunc->machine_code() !=
|
||||
// reinterpret_cast<const uint8_t*>(host_address))) condition
|
||||
// here when i distributed builds for testing, no issues arose
|
||||
// related to it but i wanted to be more explicit
|
||||
if (host_address &&
|
||||
xfunc->machine_code() !=
|
||||
reinterpret_cast<const uint8_t*>(host_address)) {
|
||||
candidate = xfunc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// we found an existing X64Function, and a return site within that
|
||||
// function that has a host address w/ native code
|
||||
if (candidate && host_address) {
|
||||
X64Backend* backend =
|
||||
static_cast<X64Backend*>(processor->backend());
|
||||
// grab the backend context, next we have to check whether the
|
||||
// guest and host stack are out of sync if they arent, its fine
|
||||
// for the backend to create a new function for the guest
|
||||
// address we're resolving if they are, it means that the reason
|
||||
// we're resolving this address is because context is being
|
||||
// restored (probably by longjmp)
|
||||
X64BackendContext* backend_context =
|
||||
backend->BackendContextForGuestContext(guest_context);
|
||||
|
||||
uint32_t current_stackpoint_index =
|
||||
backend_context->current_stackpoint_depth;
|
||||
|
||||
--current_stackpoint_index;
|
||||
|
||||
X64BackendStackpoint* stackpoints =
|
||||
backend_context->stackpoints;
|
||||
|
||||
uint32_t current_guest_stackpointer =
|
||||
static_cast<uint32_t>(guest_context->r[1]);
|
||||
uint32_t num_frames_bigger = 0;
|
||||
|
||||
/*
|
||||
if the current guest stack pointer is bigger than the
|
||||
recorded pointer for this stack thats fine, plenty of
|
||||
functions restore the original stack pointer early
|
||||
|
||||
if more than 1... we're longjmping and sure of it at
|
||||
this point (jumping to a return site that has already been
|
||||
emitted)
|
||||
*/
|
||||
while (current_stackpoint_index != 0xFFFFFFFF) {
|
||||
if (current_guest_stackpointer >
|
||||
stackpoints[current_stackpoint_index].guest_stack_) {
|
||||
--current_stackpoint_index;
|
||||
++num_frames_bigger;
|
||||
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
DEFINITELY a longjmp, return original
|
||||
host address. returning the existing host address is going to
|
||||
set off some extra machinery we have set up to support this
|
||||
|
||||
to break it down, our caller (us being
|
||||
this ResolveFunction that this comment is in) is
|
||||
X64Backend::resolve_function_thunk_ which is implemented in
|
||||
x64_backend.cc X64HelperEmitter::EmitResolveFunctionThunk, or
|
||||
a call from the resolver table
|
||||
|
||||
the x64 fastcall abi dictates that the
|
||||
stack must always be 16 byte aligned. We select our stack
|
||||
size for functions to ensure that we keep rsp aligned to 16
|
||||
bytes
|
||||
|
||||
but by calling into the body of an
|
||||
existing function we've pushed our return address onto the
|
||||
stack (dont worry about this return address, it gets
|
||||
discarded in a later step)
|
||||
|
||||
this means that the stack is no longer
|
||||
16 byte aligned, (rsp % 16) now == 8, and this is the only
|
||||
time outside of the prolog or epilog of a function that this
|
||||
will be the case
|
||||
|
||||
so, after all direct or indirect
|
||||
function calls we set
|
||||
X64Emitter::synchronize_stack_on_next_instruction_ to true.
|
||||
On the next instruction that is not
|
||||
OPCODE_SOURCE_OFFSET we will emit a check when we see
|
||||
synchronize_stack_on_next_instruction_ is true. We have to
|
||||
skip OPCODE_SOURCE_OFFSET because its not a "real"
|
||||
instruction and if we emit on it the return address of the
|
||||
function call will point to AFTER our check, so itll never be
|
||||
executed.
|
||||
|
||||
our check is just going to do test esp,
|
||||
15 to see if the stack is misaligned. (using esp instead of
|
||||
rsp saves 1 byte). We tail emit the handling for when the
|
||||
check succeeds because in 99.99999% of function calls it will
|
||||
be aligned, in the end the runtime cost of these checks is 5
|
||||
bytes for the test instruction which ought to be one cycle
|
||||
and 5 bytes for the jmp with no cycles taken for the jump
|
||||
which will be predicted not taken.
|
||||
|
||||
Our handling for the check is implemented in X64HelperEmitter::EmitGuestAndHostSynchronizeStackHelper. we don't call it directly though,
|
||||
instead we go through backend()->synchronize_guest_and_host_stack_helper_for_size(num_bytes_needed_to_represent_stack_size). we place the stack size after the
|
||||
call instruction so we can load it in the helper and readjust the return address to point after the literal value.
|
||||
|
||||
The helper is going to search the array of stackpoints to find the first one that is greater than or equal to the current stack pointer, when it finds
|
||||
the entry it will set the currently host rsp to the host stack pointer value in the entry, and then subtract the stack size of the caller from that.
|
||||
the current stackpoint index is adjusted to point to the one after the stackpoint we restored to.
|
||||
|
||||
The helper then jumps back to the function that was longjmp'ed to, with the host stack in its proper state. it just works!
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
if (num_frames_bigger > 1) {
|
||||
return host_address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auto fn = thread_state->processor()->ResolveFunction(
|
||||
static_cast<uint32_t>(target_address));
|
||||
assert_not_null(fn);
|
||||
@@ -479,7 +662,7 @@ void X64Emitter::Call(const hir::Instr* instr, GuestFunction* function) {
|
||||
mov(rcx, qword[rsp + StackLayout::GUEST_CALL_RET_ADDR]);
|
||||
|
||||
call((void*)fn->machine_code());
|
||||
|
||||
synchronize_stack_on_next_instruction_ = true;
|
||||
} else {
|
||||
// tail call
|
||||
EmitTraceUserCallReturn();
|
||||
@@ -488,8 +671,10 @@ void X64Emitter::Call(const hir::Instr* instr, GuestFunction* function) {
|
||||
mov(rcx, qword[rsp + StackLayout::GUEST_RET_ADDR]);
|
||||
|
||||
add(rsp, static_cast<uint32_t>(stack_size()));
|
||||
PopStackpoint();
|
||||
jmp((void*)fn->machine_code(), T_NEAR);
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (code_cache_->has_indirection_table()) {
|
||||
// Load the pointer to the indirection table maintained in X64CodeCache.
|
||||
@@ -513,12 +698,14 @@ void X64Emitter::Call(const hir::Instr* instr, GuestFunction* function) {
|
||||
mov(rcx, qword[rsp + StackLayout::GUEST_RET_ADDR]);
|
||||
|
||||
add(rsp, static_cast<uint32_t>(stack_size()));
|
||||
PopStackpoint();
|
||||
jmp(rax);
|
||||
} else {
|
||||
// Return address is from the previous SET_RETURN_ADDRESS.
|
||||
mov(rcx, qword[rsp + StackLayout::GUEST_CALL_RET_ADDR]);
|
||||
|
||||
call(rax);
|
||||
synchronize_stack_on_next_instruction_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,12 +744,14 @@ void X64Emitter::CallIndirect(const hir::Instr* instr,
|
||||
mov(rcx, qword[rsp + StackLayout::GUEST_RET_ADDR]);
|
||||
|
||||
add(rsp, static_cast<uint32_t>(stack_size()));
|
||||
PopStackpoint();
|
||||
jmp(rax);
|
||||
} else {
|
||||
// Return address is from the previous SET_RETURN_ADDRESS.
|
||||
mov(rcx, qword[rsp + StackLayout::GUEST_CALL_RET_ADDR]);
|
||||
|
||||
call(rax);
|
||||
synchronize_stack_on_next_instruction_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1458,6 +1647,126 @@ Xbyak::Address X64Emitter::GetBackendFlagsPtr() const {
|
||||
pt.setBit(32);
|
||||
return pt;
|
||||
}
|
||||
|
||||
void X64Emitter::HandleStackpointOverflowError(ppc::PPCContext* context) {
|
||||
// context->lr
|
||||
// todo: show lr in message?
|
||||
xe::FatalError(
|
||||
"Overflowed stackpoints! Please report this error for this title to "
|
||||
"Xenia developers.");
|
||||
}
|
||||
|
||||
void X64Emitter::PushStackpoint() {
|
||||
if (!cvars::enable_host_guest_stack_synchronization) {
|
||||
return;
|
||||
}
|
||||
// push the current host and guest stack pointers
|
||||
// this is done before a stack frame is set up or any guest instructions are
|
||||
// executed this code is probably the most intrusive part of the stackpoint
|
||||
mov(rbx, GetBackendCtxPtr(offsetof(X64BackendContext, stackpoints)));
|
||||
mov(eax,
|
||||
GetBackendCtxPtr(offsetof(X64BackendContext, current_stackpoint_depth)));
|
||||
|
||||
mov(r8, qword[GetContextReg() + offsetof(ppc::PPCContext, r[1])]);
|
||||
|
||||
imul(r9d, eax, sizeof(X64BackendStackpoint));
|
||||
add(rbx, r9);
|
||||
|
||||
mov(qword[rbx + offsetof(X64BackendStackpoint, host_stack_)], rsp);
|
||||
mov(dword[rbx + offsetof(X64BackendStackpoint, guest_stack_)], r8d);
|
||||
if (IsFeatureEnabled(kX64FlagsIndependentVars)) {
|
||||
inc(eax);
|
||||
} else {
|
||||
add(eax, 1);
|
||||
}
|
||||
|
||||
mov(GetBackendCtxPtr(offsetof(X64BackendContext, current_stackpoint_depth)),
|
||||
eax);
|
||||
|
||||
cmp(eax, (uint32_t)cvars::max_stackpoints);
|
||||
|
||||
Xbyak::Label& overflowed_stackpoints =
|
||||
AddToTail([](X64Emitter& e, Xbyak::Label& our_tail_label) {
|
||||
e.L(our_tail_label);
|
||||
// we never subtracted anything from rsp, so our stack is misaligned and
|
||||
// will fault in guesttohostthunk
|
||||
// e.sub(e.rsp, 8);
|
||||
e.push(e.rax); // easier realign, 1 byte opcode vs 4 bytes for sub
|
||||
|
||||
e.CallNativeSafe((void*)X64Emitter::HandleStackpointOverflowError);
|
||||
});
|
||||
jge(overflowed_stackpoints, T_NEAR);
|
||||
}
|
||||
void X64Emitter::PopStackpoint() {
|
||||
if (!cvars::enable_host_guest_stack_synchronization) {
|
||||
return;
|
||||
}
|
||||
// todo: maybe verify that rsp and r1 == the stackpoint?
|
||||
Xbyak::Address stackpoint_pos_pointer =
|
||||
GetBackendCtxPtr(offsetof(X64BackendContext, current_stackpoint_depth));
|
||||
stackpoint_pos_pointer.setBit(32);
|
||||
dec(stackpoint_pos_pointer);
|
||||
}
|
||||
|
||||
void X64Emitter::EnsureSynchronizedGuestAndHostStack() {
|
||||
if (!cvars::enable_host_guest_stack_synchronization) {
|
||||
return;
|
||||
}
|
||||
// chrispy: keeping this old slower test here in case in the future changes
|
||||
// need to be made
|
||||
// that result in the stack not being 8 byte misaligned on context reentry
|
||||
|
||||
#if 0
|
||||
Xbyak::Label skip{};
|
||||
mov(r8, qword[GetContextReg() + offsetof(ppc::PPCContext, r[1])]);
|
||||
mov(rbx, GetBackendCtxPtr(offsetof(X64BackendContext, stackpoints)));
|
||||
imul(eax,
|
||||
GetBackendCtxPtr(offsetof(X64BackendContext, current_stackpoint_depth)),
|
||||
sizeof(X64BackendStackpoint));
|
||||
sub(eax, sizeof(X64BackendStackpoint));
|
||||
add(rbx, rax);
|
||||
|
||||
cmp(r8d, dword[rbx + offsetof(X64BackendStackpoint, guest_stack_)]);
|
||||
jle(skip, T_NEAR);
|
||||
Xbyak::Label skip{};
|
||||
mov(r11d, stack_size());
|
||||
call(backend_->synchronize_guest_and_host_stack_helper());
|
||||
L(skip);
|
||||
#endif
|
||||
|
||||
Xbyak::Label& return_from_sync = this->NewCachedLabel();
|
||||
|
||||
// if we got here somehow from setjmp or the like we ought to have a
|
||||
// misaligned stack right now! this provides us with a very fast pretest for
|
||||
// this condition
|
||||
test(esp, 15);
|
||||
|
||||
Xbyak::Label& sync_label = this->AddToTail(
|
||||
[&return_from_sync](X64Emitter& e, Xbyak::Label& our_tail_label) {
|
||||
e.L(our_tail_label);
|
||||
|
||||
uint32_t stack32 = static_cast<uint32_t>(e.stack_size());
|
||||
auto backend = e.backend();
|
||||
|
||||
if (stack32 < 256) {
|
||||
e.call(backend->synchronize_guest_and_host_stack_helper_for_size(1));
|
||||
e.db(stack32);
|
||||
|
||||
} else if (stack32 < 65536) {
|
||||
e.call(backend->synchronize_guest_and_host_stack_helper_for_size(2));
|
||||
e.dw(stack32);
|
||||
} else {
|
||||
// ought to be impossible, a host stack bigger than 65536??
|
||||
e.call(backend->synchronize_guest_and_host_stack_helper_for_size(4));
|
||||
e.dd(stack32);
|
||||
}
|
||||
e.jmp(return_from_sync, T_NEAR);
|
||||
});
|
||||
|
||||
jnz(sync_label, T_NEAR);
|
||||
|
||||
L(return_from_sync);
|
||||
}
|
||||
} // namespace x64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
|
||||
@@ -299,6 +299,11 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||
|
||||
Xbyak::Label& AddToTail(TailEmitCallback callback, uint32_t alignment = 0);
|
||||
Xbyak::Label& NewCachedLabel();
|
||||
|
||||
void PushStackpoint();
|
||||
void PopStackpoint();
|
||||
|
||||
void EnsureSynchronizedGuestAndHostStack();
|
||||
FunctionDebugInfo* debug_info() const { return debug_info_; }
|
||||
|
||||
size_t stack_size() const { return stack_size_; }
|
||||
@@ -381,13 +386,14 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||
bool Emit(hir::HIRBuilder* builder, EmitFunctionInfo& func_info);
|
||||
void EmitGetCurrentThreadId();
|
||||
void EmitTraceUserCallReturn();
|
||||
|
||||
static void HandleStackpointOverflowError(ppc::PPCContext* context);
|
||||
protected:
|
||||
Processor* processor_ = nullptr;
|
||||
X64Backend* backend_ = nullptr;
|
||||
X64CodeCache* code_cache_ = nullptr;
|
||||
XbyakAllocator* allocator_ = nullptr;
|
||||
XexModule* guest_module_ = nullptr;
|
||||
bool synchronize_stack_on_next_instruction_ = false;
|
||||
Xbyak::util::Cpu cpu_;
|
||||
uint64_t feature_flags_ = 0;
|
||||
uint32_t current_guest_function_ = 0;
|
||||
|
||||
Reference in New Issue
Block a user