mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 09:00:17 +02:00
16 files changed
+2644
-7
No files matched your search
@@ -87,6 +87,8 @@ namespace FEXCore::Context {
|
||||
|
||||
ExitReason RunUntilExit() override;
|
||||
|
||||
void ExecuteThread(FEXCore::Core::InternalThreadState *Thread) override;
|
||||
|
||||
void CompileRIP(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP) override;
|
||||
|
||||
int GetProgramStatus() const override;
|
||||
|
||||
@@ -561,6 +561,10 @@ namespace FEXCore::Context {
|
||||
}
|
||||
}
|
||||
|
||||
void ContextImpl::ExecuteThread(FEXCore::Core::InternalThreadState *Thread) {
|
||||
Dispatcher->ExecuteDispatch(Thread->CurrentFrame);
|
||||
}
|
||||
|
||||
int ContextImpl::GetProgramStatus() const {
|
||||
return ParentThread->StatusCode;
|
||||
}
|
||||
|
||||
@@ -71,11 +71,6 @@ void OpDispatchBuilder::SyscallOp(OpcodeArgs) {
|
||||
FEXCore::X86State::REG_RSP,
|
||||
};
|
||||
|
||||
static constexpr SyscallArray GPRIndexes_Win32 = {
|
||||
FEXCore::X86State::REG_RAX,
|
||||
FEXCore::X86State::REG_RSP,
|
||||
};
|
||||
|
||||
SyscallFlags DefaultSyscallFlags = FEXCore::IR::SyscallFlags::DEFAULT;
|
||||
|
||||
const auto OSABI = CTX->SyscallHandler->GetOSABI();
|
||||
@@ -93,8 +88,9 @@ void OpDispatchBuilder::SyscallOp(OpcodeArgs) {
|
||||
DefaultSyscallFlags = FEXCore::IR::SyscallFlags::NORETURNEDRESULT;
|
||||
}
|
||||
else if (OSABI == FEXCore::HLE::SyscallOSABI::OS_WIN32) {
|
||||
NumArguments = 2;
|
||||
GPRIndexes = &GPRIndexes_Win32;
|
||||
// Since the whole context is going to be saved at entry anyway, theres no need to do additional work to pass in args
|
||||
NumArguments = 0;
|
||||
GPRIndexes = nullptr;
|
||||
DefaultSyscallFlags = FEXCore::IR::SyscallFlags::NORETURNEDRESULT;
|
||||
}
|
||||
else if (OSABI == FEXCore::HLE::SyscallOSABI::OS_HANGOVER) {
|
||||
|
||||
@@ -176,6 +176,11 @@ namespace FEXCore::Context {
|
||||
*/
|
||||
FEX_DEFAULT_VISIBILITY virtual ExitReason RunUntilExit() = 0;
|
||||
|
||||
/**
|
||||
* @brief Executes the supplied thread context on the current thread until a return is requested
|
||||
*/
|
||||
FEX_DEFAULT_VISIBILITY virtual void ExecuteThread(FEXCore::Core::InternalThreadState *Thread) = 0;
|
||||
|
||||
FEX_DEFAULT_VISIBILITY virtual void CompileRIP(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP) = 0;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
add_subdirectory(Common/)
|
||||
add_subdirectory(Tools/)
|
||||
|
||||
if (MINGW_BUILD)
|
||||
add_subdirectory(Windows/)
|
||||
endif()
|
||||
@@ -0,0 +1,19 @@
|
||||
function(build_implib name)
|
||||
add_custom_target(${name}lib ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/lib${name}.a)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lib${name}.a
|
||||
COMMAND ${CMAKE_DLLTOOL} -d ${CMAKE_CURRENT_SOURCE_DIR}/Defs/${name}.def -k -l lib${name}.a
|
||||
COMMENT "Building lib${name}.a"
|
||||
)
|
||||
|
||||
add_library(${name} SHARED IMPORTED)
|
||||
set_property(TARGET ${name} PROPERTY IMPORTED_IMPLIB ${CMAKE_CURRENT_BINARY_DIR}/lib${name}.a)
|
||||
add_dependencies(${name} ${name}lib)
|
||||
endfunction()
|
||||
|
||||
build_implib(ntdll)
|
||||
build_implib(wow64)
|
||||
|
||||
if (_M_ARM_64)
|
||||
add_subdirectory(WOW64)
|
||||
endif()
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,33 @@
|
||||
; File generated automatically from wine/dlls/wow64/wow64.spec; do not edit!
|
||||
; To generate: winebuild --def -E wine/dlls/wow64/wow64.spec > wow64.def
|
||||
|
||||
LIBRARY wow64.dll
|
||||
|
||||
EXPORTS
|
||||
Wow64AllocThreadHeap @1 PRIVATE
|
||||
Wow64AllocateHeap @2 PRIVATE
|
||||
Wow64AllocateTemp @3
|
||||
Wow64ApcRoutine @4
|
||||
Wow64CheckIfNXEnabled @5 PRIVATE
|
||||
Wow64EmulateAtlThunk @6 PRIVATE
|
||||
Wow64FreeHeap @7 PRIVATE
|
||||
Wow64FreeThreadHeap @8 PRIVATE
|
||||
Wow64GetWow64ImageOption @9 PRIVATE
|
||||
Wow64IsControlFlowGuardEnforced @10 PRIVATE
|
||||
Wow64IsStackExtentsCheckEnforced @11 PRIVATE
|
||||
Wow64KiUserCallbackDispatcher @12
|
||||
Wow64LdrpInitialize @13
|
||||
Wow64LogPrint @14 PRIVATE
|
||||
Wow64NotifyUnsimulateComplete @15 PRIVATE
|
||||
Wow64PassExceptionToGuest @16
|
||||
Wow64PrepareForDebuggerAttach @17 PRIVATE
|
||||
Wow64PrepareForException @18
|
||||
Wow64RaiseException @19
|
||||
Wow64ShallowThunkAllocObjectAttributes32TO64_FNC @20 PRIVATE
|
||||
Wow64ShallowThunkAllocSecurityQualityOfService32TO64_FNC @21 PRIVATE
|
||||
Wow64ShallowThunkSIZE_T32TO64 @22 PRIVATE
|
||||
Wow64ShallowThunkSIZE_T64TO32 @23 PRIVATE
|
||||
Wow64SuspendLocalThread @24
|
||||
Wow64SystemServiceEx @25
|
||||
Wow64ValidateUserCallTarget @26 PRIVATE
|
||||
Wow64ValidateUserCallTargetFilter @27 PRIVATE
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include <windef.h>
|
||||
#include <ntstatus.h>
|
||||
#include <winternl.h>
|
||||
|
||||
extern "C" {
|
||||
void STDMETHODCALLTYPE BTCpuProcessInit();
|
||||
NTSTATUS STDMETHODCALLTYPE BTCpuThreadInit();
|
||||
NTSTATUS STDMETHODCALLTYPE BTCpuThreadTerm(HANDLE Thread);
|
||||
void STDMETHODCALLTYPE *BTCpuGetBopCode();
|
||||
void STDMETHODCALLTYPE *__wine_get_unix_opcode();
|
||||
NTSTATUS STDMETHODCALLTYPE BTCpuGetContext(HANDLE Thread, HANDLE Process, void *Unknown, WOW64_CONTEXT *Context);
|
||||
NTSTATUS STDMETHODCALLTYPE BTCpuSetContext(HANDLE Thread, HANDLE Process, void *Unknown, WOW64_CONTEXT *Context);
|
||||
void STDMETHODCALLTYPE BTCpuSimulate();
|
||||
NTSTATUS STDMETHODCALLTYPE BTCpuSuspendLocalThread(HANDLE Thread, ULONG *Count);
|
||||
NTSTATUS STDMETHODCALLTYPE BTCpuResetToConsistentState(EXCEPTION_POINTERS *Ptrs);
|
||||
void STDMETHODCALLTYPE BTCpuFlushInstructionCache2(const void *Address, SIZE_T Size);
|
||||
void STDMETHODCALLTYPE BTCpuNotifyMemoryAlloc(void *Address, SIZE_T Size, ULONG Type, ULONG Prot);
|
||||
void STDMETHODCALLTYPE BTCpuNotifyMemoryProtect(void *Address, SIZE_T Size, ULONG NewProt);
|
||||
void STDMETHODCALLTYPE BTCpuNotifyMemoryFree(void *Address, SIZE_T Size, ULONG FreeType);
|
||||
void STDMETHODCALLTYPE BTCpuNotifyUnmapViewOfSection(void *Address, ULONG Flags);
|
||||
BOOLEAN STDMETHODCALLTYPE BTCpuIsProcessorFeaturePresent(UINT Feature);
|
||||
BOOLEAN STDMETHODCALLTYPE BTCpuUpdateProcessorInformation(SYSTEM_CPU_INFORMATION *Info);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
add_library(wow64fex SHARED
|
||||
Module.cpp
|
||||
libwow64fex.def
|
||||
)
|
||||
|
||||
target_include_directories(wow64fex PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/Source/Windows/include/"
|
||||
"${CMAKE_SOURCE_DIR}/Source/"
|
||||
)
|
||||
|
||||
target_link_libraries(wow64fex
|
||||
PRIVATE
|
||||
FEXCore
|
||||
Common
|
||||
CommonTools
|
||||
wow64
|
||||
ntdll
|
||||
)
|
||||
|
||||
target_link_options(wow64fex PRIVATE "LINKER:--image-base,0x6f100000")
|
||||
|
||||
install(TARGETS wow64fex
|
||||
RUNTIME
|
||||
DESTINATION bin
|
||||
COMPONENT runtime)
|
||||
@@ -0,0 +1,145 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
|
||||
#include <FEXCore/fextl/vector.h>
|
||||
|
||||
template<typename SizeType>
|
||||
class IntervalList {
|
||||
public:
|
||||
using DifferenceType = decltype(std::declval<SizeType>() - std::declval<SizeType>());
|
||||
|
||||
struct Interval {
|
||||
SizeType Offset;
|
||||
SizeType End;
|
||||
|
||||
Interval() = default;
|
||||
|
||||
Interval(SizeType Offset, SizeType End) : Offset{Offset}, End{End} {}
|
||||
};
|
||||
|
||||
private:
|
||||
fextl::vector<Interval> Intervals; ///< list of intervals sorted by their end offset
|
||||
|
||||
public:
|
||||
struct QueryResult {
|
||||
bool Enclosed; ///< If the given offset was enclosed by an interval
|
||||
DifferenceType Size; ///< Size of the interval starting from the query offset, or distance to the next interval if
|
||||
/// `Enclosed` is false (if there is no next interval, size is 0)
|
||||
};
|
||||
|
||||
void Clear() {
|
||||
Intervals.clear();
|
||||
}
|
||||
|
||||
void Insert(Interval Entry) {
|
||||
if (Entry.Offset == Entry.End) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto [FirstIt, EndIt] = std::equal_range(Intervals.begin(), Intervals.end(), Entry, [](const auto &LHS, const auto &RHS) {
|
||||
return LHS.End <= RHS.Offset;
|
||||
});
|
||||
|
||||
if (FirstIt == EndIt) {
|
||||
// No overlaps
|
||||
Intervals.insert(FirstIt, Entry);
|
||||
return;
|
||||
}
|
||||
|
||||
auto LastIt = std::prev(EndIt);
|
||||
// FirstIt/LastIt are the lowest/highest offset intervals respectively that overlap with the new interval
|
||||
|
||||
const SizeType Offset = std::min(Entry.Offset, FirstIt->Offset);
|
||||
const SizeType End = std::max(LastIt->End, Entry.End);
|
||||
|
||||
// Erase all overlapping entries but the first
|
||||
const auto EraseStartIt = std::next(FirstIt);
|
||||
const auto EraseEndIt = std::next(LastIt);
|
||||
LastIt = Intervals.erase(EraseStartIt, EraseEndIt);
|
||||
FirstIt = std::prev(LastIt);
|
||||
|
||||
FirstIt->Offset = Offset;
|
||||
FirstIt->End = End;
|
||||
}
|
||||
|
||||
void Remove(Interval Entry) {
|
||||
if (Entry.Offset == Entry.End) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto [FirstIt, EndIt] = std::equal_range(Intervals.begin(), Intervals.end(), Entry, [](const auto &LHS, const auto &RHS) {
|
||||
return LHS.End <= RHS.Offset;
|
||||
});
|
||||
|
||||
if (FirstIt == EndIt) {
|
||||
// No intersecting intervals present, nothing more to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (FirstIt->Offset < Entry.Offset && FirstIt->End > Entry.End) {
|
||||
// The interval to be removed is fully enclosed by an existing interval
|
||||
|
||||
// Break the single interval into two smaller intervals on either side on the interval being removed
|
||||
const auto FirstPredecessorIt = Intervals.insert(FirstIt, *FirstIt);
|
||||
FirstIt = std::next(FirstPredecessorIt);
|
||||
FirstPredecessorIt->End = Entry.Offset;
|
||||
FirstIt->Offset = Entry.End;
|
||||
return;
|
||||
}
|
||||
|
||||
auto LastIt = std::prev(EndIt);
|
||||
// FirstIt/LastIt are the lowest/highest offset intervals respectively that overlap with the new interval
|
||||
|
||||
if (FirstIt->Offset < Entry.Offset) {
|
||||
// The first overlap straddles the start of the interval to be removed
|
||||
FirstIt->End = Entry.Offset;
|
||||
if (FirstIt == LastIt) {
|
||||
// No more overlaps left, nothing more to do
|
||||
return;
|
||||
} else {
|
||||
FirstIt++;
|
||||
}
|
||||
}
|
||||
|
||||
if (LastIt->End > Entry.End) {
|
||||
// The last overlap straddles the end of the interval to be removed
|
||||
LastIt->Offset = Entry.End;
|
||||
if (LastIt == FirstIt) {
|
||||
// No more overlaps left, nothing more to do
|
||||
return;
|
||||
} else {
|
||||
LastIt--;
|
||||
}
|
||||
}
|
||||
|
||||
// Now none of the overlaps straddle the edges of the interval to be removed they can all be erased
|
||||
const auto EraseStartIt = FirstIt;
|
||||
const auto EraseEndIt = std::next(LastIt);
|
||||
Intervals.erase(EraseStartIt, EraseEndIt);
|
||||
}
|
||||
|
||||
QueryResult Query(SizeType Offset) {
|
||||
const auto It = std::upper_bound(Intervals.begin(), Intervals.end(), Offset, [](const auto &LHS, const auto &RHS) {
|
||||
return LHS < RHS.End;
|
||||
}); // Lowest offset interval that (maybe) overlaps with the query offset
|
||||
|
||||
if (It == Intervals.end()) { // No overlaps past offset
|
||||
return {false, {}};
|
||||
} else if (It->Offset > Offset) { // No overlap, return the distance to the next possible overlap
|
||||
return {false, It->Offset - Offset};
|
||||
} else { // Overlap, return the distance to the end of the overlap
|
||||
return {true, It->End - Offset};
|
||||
}
|
||||
}
|
||||
|
||||
bool Intersect(Interval Entry) {
|
||||
const auto It = std::upper_bound(Intervals.begin(), Intervals.end(), Entry, [](const auto &LHS, const auto &RHS) {
|
||||
return LHS.Offset < RHS.End;
|
||||
}); // Lowest offset interval that (maybe) overlaps with the query offset
|
||||
|
||||
return It != Intervals.end() && It->Offset < Entry.End;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,840 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
/*
|
||||
$info$
|
||||
tags: Bin|WOW64
|
||||
desc: Implements the WOW64 BT module API using FEXCore
|
||||
$end_info$
|
||||
*/
|
||||
|
||||
// Thanks to André Zwing, whose ideas from https://github.com/AndreRH/hangover this code is based upon
|
||||
|
||||
#include <FEXCore/fextl/fmt.h>
|
||||
#include <FEXCore/Core/X86Enums.h>
|
||||
#include <FEXCore/Core/SignalDelegator.h>
|
||||
#include <FEXCore/Core/Context.h>
|
||||
#include <FEXCore/Core/CoreState.h>
|
||||
#include <FEXCore/Debug/InternalThreadState.h>
|
||||
#include <FEXCore/HLE/SyscallHandler.h>
|
||||
#include <FEXCore/Config/Config.h>
|
||||
#include <FEXCore/Utils/Allocator.h>
|
||||
#include <FEXCore/Utils/LogManager.h>
|
||||
#include <FEXCore/Utils/Threads.h>
|
||||
#include <FEXCore/Utils/EnumOperators.h>
|
||||
#include <FEXCore/Utils/EnumUtils.h>
|
||||
#include <FEXCore/Utils/FPState.h>
|
||||
#include <FEXCore/Utils/ArchHelpers/Arm64.h>
|
||||
#include <FEXHeaderUtils/TypeDefines.h>
|
||||
|
||||
#include "Common/Config.h"
|
||||
#include "DummyHandlers.h"
|
||||
#include "BTInterface.h"
|
||||
#include "IntervalList.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <ntstatus.h>
|
||||
#include <windef.h>
|
||||
#include <winternl.h>
|
||||
#include <wine/debug.h>
|
||||
#include <wine/unixlib.h>
|
||||
|
||||
namespace ControlBits {
|
||||
// When this is unset, a thread can be safely interrupted and have its context recovered
|
||||
// IMPORTANT: This can only safely be written by the owning thread
|
||||
static constexpr uint32_t IN_JIT{1U << 0};
|
||||
|
||||
// JIT entry polls this bit until it is unset, at which point CONTROL_IN_JIT will be set
|
||||
static constexpr uint32_t PAUSED{1U << 1};
|
||||
|
||||
// When this is set, the CPU context stored in the CPU area has not yet been flushed to the FEX TLS
|
||||
static constexpr uint32_t WOW_CPU_AREA_DIRTY{1U << 2};
|
||||
};
|
||||
|
||||
struct TLS {
|
||||
enum class Slot : size_t {
|
||||
ENTRY_CONTEXT = WOW64_TLS_MAX_NUMBER,
|
||||
CONTROL_WORD = WOW64_TLS_MAX_NUMBER - 1,
|
||||
THREAD_STATE = WOW64_TLS_MAX_NUMBER - 2,
|
||||
};
|
||||
|
||||
_TEB *TEB;
|
||||
|
||||
explicit TLS(_TEB *TEB) : TEB(TEB) {}
|
||||
|
||||
std::atomic<uint32_t> &ControlWord() const {
|
||||
// TODO: Change this when libc++ gains std::atomic_ref support
|
||||
return reinterpret_cast<std::atomic<uint32_t> &>(TEB->TlsSlots[FEXCore::ToUnderlying(Slot::CONTROL_WORD)]);
|
||||
}
|
||||
|
||||
CONTEXT *&EntryContext() const {
|
||||
return reinterpret_cast<CONTEXT *&>(TEB->TlsSlots[FEXCore::ToUnderlying(Slot::ENTRY_CONTEXT)]);
|
||||
}
|
||||
|
||||
FEXCore::Core::InternalThreadState *&ThreadState() const {
|
||||
return reinterpret_cast<FEXCore::Core::InternalThreadState *&>(TEB->TlsSlots[FEXCore::ToUnderlying(Slot::THREAD_STATE)]);
|
||||
}
|
||||
};
|
||||
|
||||
class WowSyscallHandler;
|
||||
|
||||
namespace {
|
||||
namespace BridgeInstrs {
|
||||
// These directly jumped to by the guest to make system calls
|
||||
uint16_t Syscall{0x2ecd};
|
||||
uint16_t UnixCall{0x2ecd};
|
||||
}
|
||||
|
||||
fextl::unique_ptr<FEXCore::Context::Context> CTX;
|
||||
fextl::unique_ptr<FEX::DummyHandlers::DummySignalDelegator> SignalDelegator;
|
||||
fextl::unique_ptr<WowSyscallHandler> SyscallHandler;
|
||||
|
||||
SYSTEM_CPU_INFORMATION CpuInfo{};
|
||||
|
||||
std::mutex ThreadSuspendLock;
|
||||
|
||||
std::pair<NTSTATUS, TLS> GetThreadTLS(HANDLE Thread) {
|
||||
THREAD_BASIC_INFORMATION Info;
|
||||
const NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr);
|
||||
return {Err, TLS{reinterpret_cast<_TEB *>(Info.TebBaseAddress)}};
|
||||
}
|
||||
|
||||
TLS GetTLS() {
|
||||
return TLS{NtCurrentTeb()};
|
||||
}
|
||||
|
||||
uint64_t GetWowTEB(void *TEB) {
|
||||
static constexpr size_t WowTEBOffsetMemberOffset{0x180c};
|
||||
return static_cast<uint64_t>(*reinterpret_cast<LONG *>(reinterpret_cast<uintptr_t>(TEB) + WowTEBOffsetMemberOffset)
|
||||
+ reinterpret_cast<uint64_t>(TEB));
|
||||
}
|
||||
|
||||
bool IsAddressInJit(uint64_t Address) {
|
||||
return GetTLS().ThreadState()->CPUBackend->IsAddressInCodeBuffer(Address);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Context {
|
||||
void LoadStateFromWowContext(FEXCore::Core::InternalThreadState *Thread, uint64_t WowTEB, WOW64_CONTEXT *Context) {
|
||||
auto &State = Thread->CurrentFrame->State;
|
||||
|
||||
// General register state
|
||||
|
||||
State.gregs[FEXCore::X86State::REG_RAX] = Context->Eax;
|
||||
State.gregs[FEXCore::X86State::REG_RBX] = Context->Ebx;
|
||||
State.gregs[FEXCore::X86State::REG_RCX] = Context->Ecx;
|
||||
State.gregs[FEXCore::X86State::REG_RDX] = Context->Edx;
|
||||
State.gregs[FEXCore::X86State::REG_RSI] = Context->Esi;
|
||||
State.gregs[FEXCore::X86State::REG_RDI] = Context->Edi;
|
||||
State.gregs[FEXCore::X86State::REG_RBP] = Context->Ebp;
|
||||
State.gregs[FEXCore::X86State::REG_RSP] = Context->Esp;
|
||||
|
||||
State.rip = Context->Eip;
|
||||
CTX->SetFlagsFromCompactedEFLAGS(Thread, Context->EFlags);
|
||||
|
||||
State.es_idx = Context->SegEs & 0xffff;
|
||||
State.cs_idx = Context->SegCs & 0xffff;
|
||||
State.ss_idx = Context->SegSs & 0xffff;
|
||||
State.ds_idx = Context->SegDs & 0xffff;
|
||||
State.fs_idx = Context->SegFs & 0xffff;
|
||||
State.gs_idx = Context->SegGs & 0xffff;
|
||||
|
||||
// The TEB is the only populated GDT entry by default
|
||||
State.gdt[(Context->SegFs & 0xffff) >> 3].base = WowTEB;
|
||||
State.fs_cached = WowTEB;
|
||||
State.es_cached = 0;
|
||||
State.cs_cached = 0;
|
||||
State.ss_cached = 0;
|
||||
State.ds_cached = 0;
|
||||
|
||||
// Floating-point register state
|
||||
const auto *XSave = reinterpret_cast<XSAVE_FORMAT*>(Context->ExtendedRegisters);
|
||||
|
||||
memcpy(State.xmm.sse.data, XSave->XmmRegisters, sizeof(State.xmm.sse.data));
|
||||
memcpy(State.mm, XSave->FloatRegisters, sizeof(State.mm));
|
||||
|
||||
State.FCW = XSave->ControlWord;
|
||||
State.flags[FEXCore::X86State::X87FLAG_C0_LOC] = (XSave->StatusWord >> 8) & 1;
|
||||
State.flags[FEXCore::X86State::X87FLAG_C1_LOC] = (XSave->StatusWord >> 9) & 1;
|
||||
State.flags[FEXCore::X86State::X87FLAG_C2_LOC] = (XSave->StatusWord >> 10) & 1;
|
||||
State.flags[FEXCore::X86State::X87FLAG_C3_LOC] = (XSave->StatusWord >> 14) & 1;
|
||||
State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] = (XSave->StatusWord >> 11) & 0b111;
|
||||
State.AbridgedFTW = XSave->TagWord;
|
||||
}
|
||||
|
||||
void StoreWowContextFromState(FEXCore::Core::InternalThreadState *Thread, WOW64_CONTEXT *Context) {
|
||||
auto &State = Thread->CurrentFrame->State;
|
||||
|
||||
// General register state
|
||||
|
||||
Context->Eax = State.gregs[FEXCore::X86State::REG_RAX];
|
||||
Context->Ebx = State.gregs[FEXCore::X86State::REG_RBX];
|
||||
Context->Ecx = State.gregs[FEXCore::X86State::REG_RCX];
|
||||
Context->Edx = State.gregs[FEXCore::X86State::REG_RDX];
|
||||
Context->Esi = State.gregs[FEXCore::X86State::REG_RSI];
|
||||
Context->Edi = State.gregs[FEXCore::X86State::REG_RDI];
|
||||
Context->Ebp = State.gregs[FEXCore::X86State::REG_RBP];
|
||||
Context->Esp = State.gregs[FEXCore::X86State::REG_RSP];
|
||||
|
||||
Context->Eip = State.rip;
|
||||
Context->EFlags = CTX->ReconstructCompactedEFLAGS(Thread);
|
||||
|
||||
Context->SegEs = State.es_idx;
|
||||
Context->SegCs = State.cs_idx;
|
||||
Context->SegSs = State.ss_idx;
|
||||
Context->SegDs = State.ds_idx;
|
||||
Context->SegFs = State.fs_idx;
|
||||
Context->SegGs = State.gs_idx;
|
||||
|
||||
// Floating-point register state
|
||||
|
||||
auto *XSave = reinterpret_cast<XSAVE_FORMAT*>(Context->ExtendedRegisters);
|
||||
|
||||
memcpy(XSave->XmmRegisters, State.xmm.sse.data, sizeof(State.xmm.sse.data));
|
||||
memcpy(XSave->FloatRegisters, State.mm, sizeof(State.mm));
|
||||
|
||||
XSave->ControlWord = State.FCW;
|
||||
XSave->StatusWord =
|
||||
(State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] << 11) |
|
||||
(State.flags[FEXCore::X86State::X87FLAG_C0_LOC] << 8) |
|
||||
(State.flags[FEXCore::X86State::X87FLAG_C1_LOC] << 9) |
|
||||
(State.flags[FEXCore::X86State::X87FLAG_C2_LOC] << 10) |
|
||||
(State.flags[FEXCore::X86State::X87FLAG_C3_LOC] << 14);
|
||||
XSave->TagWord = State.AbridgedFTW;
|
||||
|
||||
Context->FloatSave.ControlWord = XSave->ControlWord;
|
||||
Context->FloatSave.StatusWord = XSave->StatusWord;
|
||||
Context->FloatSave.TagWord = FEXCore::FPState::ConvertFromAbridgedFTW(XSave->StatusWord, State.mm, XSave->TagWord);
|
||||
Context->FloatSave.ErrorOffset = XSave->ErrorOffset;
|
||||
Context->FloatSave.ErrorSelector = XSave->ErrorSelector | (XSave->ErrorOpcode << 16);
|
||||
Context->FloatSave.DataOffset = XSave->DataOffset;
|
||||
Context->FloatSave.DataSelector = XSave->DataSelector;
|
||||
Context->FloatSave.Cr0NpxState = XSave->StatusWord | 0xffff0000;
|
||||
}
|
||||
|
||||
NTSTATUS FlushThreadStateContext(HANDLE Thread) {
|
||||
const auto [Err, TLS] = GetThreadTLS(Thread);
|
||||
if (Err) {
|
||||
return Err;
|
||||
}
|
||||
|
||||
WOW64_CONTEXT TmpWowContext{
|
||||
.ContextFlags = WOW64_CONTEXT_FULL | WOW64_CONTEXT_EXTENDED_REGISTERS
|
||||
};
|
||||
|
||||
Context::StoreWowContextFromState(TLS.ThreadState(), &TmpWowContext);
|
||||
return RtlWow64SetThreadContext(Thread, &TmpWowContext);
|
||||
}
|
||||
|
||||
void ReconstructThreadState(CONTEXT *Context) {
|
||||
const auto &Config = SignalDelegator->GetConfig();
|
||||
auto *Thread = GetTLS().ThreadState();
|
||||
auto &State = Thread->CurrentFrame->State;
|
||||
|
||||
State.rip = CTX->RestoreRIPFromHostPC(Thread, Context->Pc);
|
||||
|
||||
// Spill all SRA GPRs
|
||||
for (size_t i = 0; i < Config.SRAGPRCount; i++) {
|
||||
State.gregs[i] = Context->X[Config.SRAGPRMapping[i]];
|
||||
}
|
||||
|
||||
// Spill all SRA FPRs
|
||||
for (size_t i = 0; i < Config.SRAFPRCount; i++) {
|
||||
memcpy(State.xmm.sse.data[i], &Context->V[Config.SRAFPRMapping[i]], sizeof(__uint128_t));
|
||||
}
|
||||
}
|
||||
|
||||
WOW64_CONTEXT ReconstructWowContext(CONTEXT *Context) {
|
||||
ReconstructThreadState(Context);
|
||||
|
||||
WOW64_CONTEXT WowContext{
|
||||
.ContextFlags = WOW64_CONTEXT_ALL,
|
||||
};
|
||||
|
||||
auto *XSave = reinterpret_cast<XSAVE_FORMAT *>(WowContext.ExtendedRegisters);
|
||||
XSave->ControlWord = 0x27f;
|
||||
XSave->MxCsr = 0x1f80;
|
||||
|
||||
Context::StoreWowContextFromState(GetTLS().ThreadState(), &WowContext);
|
||||
return WowContext;
|
||||
}
|
||||
|
||||
bool HandleUnalignedAccess(CONTEXT *Context) {
|
||||
if (!GetTLS().ThreadState()->CPUBackend->IsAddressInCodeBuffer(Context->Pc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FEX_CONFIG_OPT(ParanoidTSO, PARANOIDTSO);
|
||||
const auto Result = FEXCore::ArchHelpers::Arm64::HandleUnalignedAccess(ParanoidTSO(), Context->Pc, &Context->X0);
|
||||
if (!Result.first) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Context->Pc += Result.second;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LockJITContext() {
|
||||
uint32_t Expected = GetTLS().ControlWord().load(), New;
|
||||
|
||||
// Spin until PAUSED is unset, setting IN_JIT when that occurs
|
||||
do {
|
||||
Expected = Expected & ~ControlBits::PAUSED;
|
||||
New = (Expected | ControlBits::IN_JIT) & ~ControlBits::WOW_CPU_AREA_DIRTY;
|
||||
} while (!GetTLS().ControlWord().compare_exchange_weak(Expected, New, std::memory_order::relaxed));
|
||||
std::atomic_signal_fence(std::memory_order::seq_cst);
|
||||
|
||||
// If the CPU area is dirty, flush it to the JIT context before reentry
|
||||
if (Expected & ControlBits::WOW_CPU_AREA_DIRTY) {
|
||||
WOW64_CONTEXT *WowContext;
|
||||
RtlWow64GetCurrentCpuArea(nullptr, reinterpret_cast<void **>(&WowContext), nullptr);
|
||||
Context::LoadStateFromWowContext(GetTLS().ThreadState(), GetWowTEB(NtCurrentTeb()), WowContext);
|
||||
}
|
||||
}
|
||||
|
||||
void UnlockJITContext() {
|
||||
std::atomic_signal_fence(std::memory_order::seq_cst);
|
||||
GetTLS().ControlWord().fetch_and(~ControlBits::IN_JIT, std::memory_order::relaxed);
|
||||
}
|
||||
|
||||
bool HandleSuspendInterrupt(CONTEXT *Context, uint64_t FaultAddress) {
|
||||
if (FaultAddress != reinterpret_cast<uint64_t>(&GetTLS().ThreadState()->InterruptFaultPage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void *TmpAddress = reinterpret_cast<void *>(FaultAddress);
|
||||
SIZE_T TmpSize = FHU::FEX_PAGE_SIZE;
|
||||
ULONG TmpProt;
|
||||
NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_READWRITE, &TmpProt);
|
||||
|
||||
// Since interrupts only happen at the start of blocks, the reconstructed state should be entirely accurate
|
||||
ReconstructThreadState(Context);
|
||||
|
||||
// Yield to the suspender
|
||||
UnlockJITContext();
|
||||
LockJITContext();
|
||||
|
||||
// Adjust context to return to the dispatcher, reloading SRA from thread state
|
||||
const auto &Config = SignalDelegator->GetConfig();
|
||||
Context->Pc = Config.AbsoluteLoopTopAddressFillSRA;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Invalidation {
|
||||
static IntervalList<uint64_t> RWXIntervals;
|
||||
static std::mutex RWXIntervalsLock;
|
||||
|
||||
void HandleMemoryProtectionNotification(uint64_t Address, uint64_t Size, ULONG Prot) {
|
||||
const auto AlignedBase = Address & FHU::FEX_PAGE_MASK;
|
||||
const auto AlignedSize = (Address - AlignedBase + Size + FHU::FEX_PAGE_SIZE - 1) & FHU::FEX_PAGE_MASK;
|
||||
|
||||
if (Prot & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) {
|
||||
CTX->InvalidateGuestCodeRange(GetTLS().ThreadState(), AlignedBase, AlignedSize);
|
||||
}
|
||||
|
||||
if (Prot & PAGE_EXECUTE_READWRITE) {
|
||||
LogMan::Msg::DFmt("Add SMC interval: {:X} - {:X}", AlignedBase, AlignedBase + AlignedSize);
|
||||
std::scoped_lock Lock(RWXIntervalsLock);
|
||||
RWXIntervals.Insert({AlignedBase, AlignedBase + AlignedSize});
|
||||
} else {
|
||||
std::scoped_lock Lock(RWXIntervalsLock);
|
||||
RWXIntervals.Remove({AlignedBase, AlignedBase + AlignedSize});
|
||||
}
|
||||
}
|
||||
|
||||
void InvalidateContainingSection(uint64_t Address, bool Free) {
|
||||
MEMORY_BASIC_INFORMATION Info;
|
||||
if (NtQueryVirtualMemory(NtCurrentProcess(), reinterpret_cast<void *>(Address), MemoryBasicInformation, &Info, sizeof(Info), nullptr))
|
||||
return;
|
||||
|
||||
const auto SectionBase = reinterpret_cast<uint64_t>(Info.AllocationBase);
|
||||
const auto SectionSize = reinterpret_cast<uint64_t>(Info.BaseAddress) + Info.RegionSize
|
||||
- reinterpret_cast<uint64_t>(Info.AllocationBase);
|
||||
CTX->InvalidateGuestCodeRange(GetTLS().ThreadState(), SectionBase, SectionSize);
|
||||
|
||||
if (Free) {
|
||||
std::scoped_lock Lock(RWXIntervalsLock);
|
||||
RWXIntervals.Remove({SectionBase, SectionBase + SectionSize});
|
||||
}
|
||||
}
|
||||
|
||||
void InvalidateAlignedInterval(uint64_t Address, uint64_t Size, bool Free) {
|
||||
const auto AlignedBase = Address & FHU::FEX_PAGE_MASK;
|
||||
const auto AlignedSize = (Address - AlignedBase + Size + FHU::FEX_PAGE_SIZE - 1) & FHU::FEX_PAGE_MASK;
|
||||
CTX->InvalidateGuestCodeRange(GetTLS().ThreadState(), AlignedBase, AlignedSize);
|
||||
|
||||
if (Free) {
|
||||
std::scoped_lock Lock(RWXIntervalsLock);
|
||||
RWXIntervals.Remove({AlignedBase, AlignedBase + AlignedSize});
|
||||
}
|
||||
}
|
||||
|
||||
void ReprotectRWXIntervals(uint64_t Address, uint64_t Size) {
|
||||
const auto End = Address + Size;
|
||||
std::scoped_lock Lock(RWXIntervalsLock);
|
||||
|
||||
do {
|
||||
const auto Query = RWXIntervals.Query(Address);
|
||||
if (Query.Enclosed) {
|
||||
void *TmpAddress = reinterpret_cast<void *>(Address);
|
||||
SIZE_T TmpSize = static_cast<SIZE_T>(std::min(End, Address + Query.Size) - Address);
|
||||
ULONG TmpProt;
|
||||
NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_EXECUTE_READ, &TmpProt);
|
||||
} else if (!Query.Size) {
|
||||
// No more regions past `Address` in the interval list
|
||||
break;
|
||||
}
|
||||
|
||||
Address += Query.Size;
|
||||
} while (Address < End);
|
||||
}
|
||||
|
||||
bool HandleRWXAccessViolation(uint64_t FaultAddress) {
|
||||
const bool NeedsInvalidate = [](uint64_t Address) {
|
||||
std::unique_lock Lock(RWXIntervalsLock);
|
||||
const bool Enclosed = RWXIntervals.Query(Address).Enclosed;
|
||||
// Invalidate just the single faulting page
|
||||
if (!Enclosed)
|
||||
return false;
|
||||
|
||||
ULONG TmpProt;
|
||||
void *TmpAddress = reinterpret_cast<void *>(Address);
|
||||
SIZE_T TmpSize = 1;
|
||||
NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_EXECUTE_READWRITE, &TmpProt);
|
||||
return true;
|
||||
}(FaultAddress);
|
||||
|
||||
if (NeedsInvalidate) {
|
||||
// RWXIntervalsLock cannot be held during invalidation
|
||||
CTX->InvalidateGuestCodeRange(GetTLS().ThreadState(), FaultAddress & FHU::FEX_PAGE_MASK, FHU::FEX_PAGE_SIZE);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Logging {
|
||||
void MsgHandler(LogMan::DebugLevels Level, char const *Message) {
|
||||
const auto Output = fextl::fmt::format("[{}][{:X}] {}\n", LogMan::DebugLevelStr(Level), GetCurrentThreadId(), Message);
|
||||
__wine_dbg_output(Output.c_str());
|
||||
}
|
||||
|
||||
void AssertHandler(char const *Message) {
|
||||
const auto Output = fextl::fmt::format("[ASSERT] {}\n", Message);
|
||||
__wine_dbg_output(Output.c_str());
|
||||
}
|
||||
|
||||
void Init() {
|
||||
LogMan::Throw::InstallHandler(AssertHandler);
|
||||
LogMan::Msg::InstallHandler(MsgHandler);
|
||||
}
|
||||
}
|
||||
|
||||
class WowSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators {
|
||||
public:
|
||||
WowSyscallHandler() {
|
||||
OSABI = FEXCore::HLE::SyscallOSABI::OS_WIN32;
|
||||
}
|
||||
|
||||
uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame *Frame, FEXCore::HLE::SyscallArguments *Args) override {
|
||||
const uint64_t ReturnRIP = *(uint32_t *)(Frame->State.gregs[FEXCore::X86State::REG_RSP]); // Return address from the stack
|
||||
uint64_t ReturnRSP = Frame->State.gregs[FEXCore::X86State::REG_RSP] + 4; // Stack pointer after popping return address
|
||||
uint64_t ReturnRAX = 0;
|
||||
|
||||
if (Frame->State.rip == (uint64_t)&BridgeInstrs::UnixCall) {
|
||||
struct StackLayout {
|
||||
unixlib_handle_t Handle;
|
||||
UINT32 ID;
|
||||
ULONG32 Args;
|
||||
} *StackArgs = reinterpret_cast<StackLayout *>(ReturnRSP);
|
||||
|
||||
ReturnRSP += sizeof(StackLayout);
|
||||
|
||||
Context::UnlockJITContext();
|
||||
ReturnRAX = static_cast<uint64_t>(__wine_unix_call(StackArgs->Handle, StackArgs->ID, ULongToPtr(StackArgs->Args)));
|
||||
Context::LockJITContext();
|
||||
} else if (Frame->State.rip == (uint64_t)&BridgeInstrs::Syscall) {
|
||||
const uint64_t EntryRAX = Frame->State.gregs[FEXCore::X86State::REG_RAX];
|
||||
|
||||
Context::UnlockJITContext();
|
||||
ReturnRAX = static_cast<uint64_t>(Wow64SystemServiceEx(static_cast<UINT>(EntryRAX),
|
||||
reinterpret_cast<UINT *>(ReturnRSP + 4)));
|
||||
Context::LockJITContext();
|
||||
|
||||
}
|
||||
// If a new context has been set, use it directly and don't return to the syscall caller
|
||||
if (Frame->State.rip == (uint64_t)&BridgeInstrs::Syscall ||
|
||||
Frame->State.rip == (uint64_t)&BridgeInstrs::UnixCall) {
|
||||
Frame->State.gregs[FEXCore::X86State::REG_RAX] = ReturnRAX;
|
||||
Frame->State.gregs[FEXCore::X86State::REG_RSP] = ReturnRSP;
|
||||
Frame->State.rip = ReturnRIP;
|
||||
}
|
||||
|
||||
// NORETURNEDRESULT causes this result to be ignored since we restore all registers back from memory after a syscall anyway
|
||||
return 0;
|
||||
}
|
||||
|
||||
FEXCore::HLE::SyscallABI GetSyscallABI(uint64_t Syscall) override {
|
||||
return { .NumArgs = 0, .HasReturn = false, .HostSyscallNumber = -1 };
|
||||
}
|
||||
|
||||
FEXCore::HLE::AOTIRCacheEntryLookupResult LookupAOTIRCacheEntry(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestAddr) override {
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
void MarkGuestExecutableRange(FEXCore::Core::InternalThreadState *Thread, uint64_t Start, uint64_t Length) override {
|
||||
Invalidation::ReprotectRWXIntervals(Start, Length);
|
||||
}
|
||||
};
|
||||
|
||||
void BTCpuProcessInit() {
|
||||
Logging::Init();
|
||||
FEX::Config::InitializeConfigs();
|
||||
FEXCore::Config::Initialize();
|
||||
FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer());
|
||||
FEXCore::Config::AddLayer(FEX::Config::CreateMainLayer());
|
||||
FEXCore::Config::Load();
|
||||
FEXCore::Config::ReloadMetaLayer();
|
||||
|
||||
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_IS_INTERPRETER, "0");
|
||||
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_INTERPRETER_INSTALLED, "0");
|
||||
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_IS64BIT_MODE, "0");
|
||||
|
||||
// Not applicable to Windows
|
||||
FEXCore::Config::EraseSet(FEXCore::Config::ConfigOption::CONFIG_TSOAUTOMIGRATION, "0");
|
||||
|
||||
FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_32BIT);
|
||||
|
||||
SignalDelegator = fextl::make_unique<FEX::DummyHandlers::DummySignalDelegator>();
|
||||
SyscallHandler = fextl::make_unique<WowSyscallHandler>();
|
||||
|
||||
CTX = FEXCore::Context::Context::CreateNewContext();
|
||||
CTX->InitializeContext();
|
||||
CTX->SetSignalDelegator(SignalDelegator.get());
|
||||
CTX->SetSyscallHandler(SyscallHandler.get());
|
||||
CTX->InitCore(0, 0);
|
||||
|
||||
CpuInfo.ProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL;
|
||||
|
||||
// Baseline FEX feature-set
|
||||
CpuInfo.ProcessorFeatureBits = CPU_FEATURE_VME | CPU_FEATURE_TSC | CPU_FEATURE_CMOV | CPU_FEATURE_PGE |
|
||||
CPU_FEATURE_PSE | CPU_FEATURE_MTRR | CPU_FEATURE_CX8 | CPU_FEATURE_MMX |
|
||||
CPU_FEATURE_X86 | CPU_FEATURE_PAT | CPU_FEATURE_FXSR | CPU_FEATURE_SEP |
|
||||
CPU_FEATURE_SSE | CPU_FEATURE_3DNOW | CPU_FEATURE_SSE2 | CPU_FEATURE_SSE3 |
|
||||
CPU_FEATURE_CX128 | CPU_FEATURE_NX | CPU_FEATURE_SSSE3 | CPU_FEATURE_SSE41 |
|
||||
CPU_FEATURE_PAE | CPU_FEATURE_DAZ;
|
||||
|
||||
// Features that require specific host CPU support
|
||||
const auto CPUIDResult01 = CTX->RunCPUIDFunction(0x01, 0);
|
||||
if (CPUIDResult01.ecx & (1 << 20)) {
|
||||
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_SSE42;
|
||||
}
|
||||
if (CPUIDResult01.ecx & (1 << 27)) {
|
||||
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_XSAVE;
|
||||
}
|
||||
if (CPUIDResult01.ecx & (1 << 28)) {
|
||||
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_AVX;
|
||||
}
|
||||
|
||||
const auto CPUIDResult07 = CTX->RunCPUIDFunction(0x07, 0);
|
||||
if (CPUIDResult07.ebx & (1 << 5)) {
|
||||
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_AVX2;
|
||||
}
|
||||
|
||||
const auto FamilyIdentifier = CPUIDResult01.eax;
|
||||
CpuInfo.ProcessorLevel = ((FamilyIdentifier >> 8) & 0xf) + ((FamilyIdentifier >> 20) & 0xff); // Family
|
||||
CpuInfo.ProcessorRevision = (FamilyIdentifier & 0xf0000) >> 4; // Extended Model
|
||||
CpuInfo.ProcessorRevision |= (FamilyIdentifier & 0xf0) << 4; // Model
|
||||
CpuInfo.ProcessorRevision |= FamilyIdentifier & 0xf; // Stepping
|
||||
}
|
||||
|
||||
NTSTATUS BTCpuThreadInit() {
|
||||
GetTLS().ThreadState() = CTX->CreateThread(nullptr, 0);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS BTCpuThreadTerm(HANDLE Thread) {
|
||||
const auto [Err, TLS] = GetThreadTLS(Thread);
|
||||
if (Err) {
|
||||
return Err;
|
||||
}
|
||||
|
||||
CTX->DestroyThread(TLS.ThreadState());
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void *BTCpuGetBopCode() {
|
||||
return &BridgeInstrs::Syscall;
|
||||
}
|
||||
|
||||
void *__wine_get_unix_opcode() {
|
||||
return &BridgeInstrs::UnixCall;
|
||||
}
|
||||
|
||||
NTSTATUS BTCpuGetContext(HANDLE Thread, HANDLE Process, void *Unknown, WOW64_CONTEXT *Context) {
|
||||
auto [Err, TLS] = GetThreadTLS(Thread);
|
||||
if (Err) {
|
||||
return Err;
|
||||
}
|
||||
|
||||
if (!(TLS.ControlWord().load(std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) {
|
||||
if (Err = Context::FlushThreadStateContext(Thread); Err) {
|
||||
return Err;
|
||||
}
|
||||
}
|
||||
|
||||
return RtlWow64GetThreadContext(Thread, Context);
|
||||
}
|
||||
|
||||
NTSTATUS BTCpuSetContext(HANDLE Thread, HANDLE Process, void *Unknown, WOW64_CONTEXT *Context) {
|
||||
auto [Err, TLS] = GetThreadTLS(Thread);
|
||||
if (Err) {
|
||||
return Err;
|
||||
}
|
||||
|
||||
|
||||
// Back-up the input context incase we've been passed the CPU area (the flush below would wipe it out otherwise)
|
||||
WOW64_CONTEXT TmpContext = *Context;
|
||||
|
||||
if (!(TLS.ControlWord().load(std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) {
|
||||
if (Err = Context::FlushThreadStateContext(Thread); Err) {
|
||||
return Err;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the input context into the CPU area then pass the full context into the JIT
|
||||
if (Err = RtlWow64SetThreadContext(Thread, &TmpContext); Err) {
|
||||
return Err;
|
||||
}
|
||||
|
||||
TmpContext.ContextFlags = WOW64_CONTEXT_FULL | WOW64_CONTEXT_EXTENDED_REGISTERS;
|
||||
|
||||
if (Err = RtlWow64GetThreadContext(Thread, &TmpContext); Err) {
|
||||
return Err;
|
||||
}
|
||||
|
||||
Context::LoadStateFromWowContext(TLS.ThreadState(), GetWowTEB(TLS.TEB), &TmpContext);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void BTCpuSimulate() {
|
||||
CONTEXT entry_context;
|
||||
RtlCaptureContext(&entry_context);
|
||||
|
||||
// APC handling calls BTCpuSimulate from syscalls and then use NtContinue to return to the previous context,
|
||||
// to avoid the saved context being clobbered in this case only save the entry context highest in the stack
|
||||
if (!GetTLS().EntryContext() || GetTLS().EntryContext()->Sp <= entry_context.Sp) {
|
||||
GetTLS().EntryContext() = &entry_context;
|
||||
}
|
||||
|
||||
Context::LockJITContext();
|
||||
CTX->ExecuteThread(GetTLS().ThreadState());
|
||||
Context::UnlockJITContext();
|
||||
}
|
||||
|
||||
NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG *Count) {
|
||||
THREAD_BASIC_INFORMATION Info;
|
||||
if (NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) {
|
||||
return Err;
|
||||
}
|
||||
|
||||
const auto ThreadTID = reinterpret_cast<uint64_t>(Info.ClientId.UniqueThread);
|
||||
if (ThreadTID == GetCurrentThreadId()) {
|
||||
LogMan::Msg::DFmt("Suspending self");
|
||||
// Mark the CPU area as dirty, to force the JIT context to be restored from it on entry as it may be changed using
|
||||
// SetThreadContext (which doesn't use the BTCpu API)
|
||||
if (!(GetTLS().ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed) &
|
||||
ControlBits::WOW_CPU_AREA_DIRTY)) {
|
||||
if (NTSTATUS Err = Context::FlushThreadStateContext(Thread); Err) {
|
||||
return Err;
|
||||
}
|
||||
}
|
||||
|
||||
return NtSuspendThread(Thread, Count);
|
||||
}
|
||||
|
||||
LogMan::Msg::DFmt("Suspending thread: {:X}", ThreadTID);
|
||||
|
||||
auto [Err, TLS] = GetThreadTLS(Thread);
|
||||
if (Err) {
|
||||
return Err;
|
||||
}
|
||||
|
||||
std::scoped_lock Lock(ThreadSuspendLock);
|
||||
// If CONTROL_IN_JIT is unset at this point, then it can never be set (and thus the JIT cannot be reentered) as
|
||||
// CONTROL_PAUSED has been set, as such, while this may redundantly request interrupts in rare cases it will never
|
||||
// miss them
|
||||
if (TLS.ControlWord().fetch_or(ControlBits::PAUSED, std::memory_order::relaxed) & ControlBits::IN_JIT) {
|
||||
LogMan::Msg::DFmt("Thread {:X} is in JIT, polling for interrupt", ThreadTID);
|
||||
|
||||
ULONG TmpProt;
|
||||
void *TmpAddress = &TLS.ThreadState()->InterruptFaultPage;
|
||||
SIZE_T TmpSize = FHU::FEX_PAGE_SIZE;
|
||||
NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_READONLY, &TmpProt);
|
||||
}
|
||||
|
||||
// Spin until the JIT is interrupted
|
||||
while (TLS.ControlWord().load() & ControlBits::IN_JIT);
|
||||
|
||||
// The JIT has now been interrupted and the context stored in the thread's CPU area is up-to-date
|
||||
if (Err = NtSuspendThread(Thread, Count); Err) {
|
||||
TLS.ControlWord().fetch_and(~ControlBits::PAUSED, std::memory_order::relaxed);
|
||||
return Err;
|
||||
}
|
||||
|
||||
CONTEXT TmpContext{
|
||||
.ContextFlags = CONTEXT_INTEGER,
|
||||
};
|
||||
|
||||
// NtSuspendThread may return before the thread is actually suspended, so a sync operation like NtGetContextThread
|
||||
// needs to be called to ensure it is before we unset CONTROL_PAUSED
|
||||
std::ignore = NtGetContextThread(Thread, &TmpContext);
|
||||
|
||||
// Mark the CPU area as dirty, to force the JIT context to be restored from it on entry as it may be changed using
|
||||
// SetThreadContext (which doesn't use the BTCpu API)
|
||||
if (!(TLS.ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) {
|
||||
if (Err = Context::FlushThreadStateContext(Thread); Err) {
|
||||
return Err;
|
||||
}
|
||||
}
|
||||
|
||||
LogMan::Msg::DFmt("Thread suspended: {:X}", ThreadTID);
|
||||
|
||||
// Now the thread is suspended on the host, unset CONTROL_PAUSED so that NtResumeThread will
|
||||
// continue execution in the JIT
|
||||
TLS.ControlWord().fetch_and(~ControlBits::PAUSED, std::memory_order::relaxed);
|
||||
|
||||
return Err;
|
||||
}
|
||||
|
||||
NTSTATUS BTCpuResetToConsistentState(EXCEPTION_POINTERS *Ptrs) {
|
||||
auto *Context = Ptrs->ContextRecord;
|
||||
const auto *Exception = Ptrs->ExceptionRecord;
|
||||
if (Exception->ExceptionCode == EXCEPTION_DATATYPE_MISALIGNMENT && Context::HandleUnalignedAccess(Context)) {
|
||||
LogMan::Msg::DFmt("Handled unaligned atomic: new pc: {:X}", Context->Pc);
|
||||
NtContinue(Context, FALSE);
|
||||
}
|
||||
|
||||
if (Exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
|
||||
const auto FaultAddress = static_cast<uint64_t>(Exception->ExceptionInformation[1]);
|
||||
|
||||
if (Invalidation::HandleRWXAccessViolation(FaultAddress)) {
|
||||
LogMan::Msg::DFmt("Handled self-modifying code: pc: {:X} fault: {:X}", Context->Pc, FaultAddress);
|
||||
NtContinue(Context, FALSE);
|
||||
}
|
||||
|
||||
if (Context::HandleSuspendInterrupt(Context, FaultAddress)) {
|
||||
LogMan::Msg::DFmt("Resumed from suspend");
|
||||
NtContinue(Context, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsAddressInJit(Context->Pc)) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
LogMan::Msg::DFmt("Reconstructing context");
|
||||
|
||||
WOW64_CONTEXT WowContext = Context::ReconstructWowContext(Context);
|
||||
LogMan::Msg::DFmt("pc: {:X} eip: {:X}", Context->Pc, WowContext.Eip);
|
||||
|
||||
BTCpuSetContext(GetCurrentThread(), GetCurrentProcess(), nullptr, &WowContext);
|
||||
Context::UnlockJITContext();
|
||||
|
||||
// Replace the host context with one captured before JIT entry so host code can unwind
|
||||
memcpy(Context, GetTLS().EntryContext(), sizeof(*Context));
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void BTCpuFlushInstructionCache2(const void *Address, SIZE_T Size) {
|
||||
Invalidation::InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), false);
|
||||
}
|
||||
|
||||
void BTCpuNotifyMemoryAlloc(void *Address, SIZE_T Size, ULONG Type, ULONG Prot) {
|
||||
Invalidation::HandleMemoryProtectionNotification(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size),
|
||||
Prot);
|
||||
}
|
||||
|
||||
void BTCpuNotifyMemoryProtect(void *Address, SIZE_T Size, ULONG NewProt) {
|
||||
Invalidation::HandleMemoryProtectionNotification(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size),
|
||||
NewProt);
|
||||
}
|
||||
|
||||
void BTCpuNotifyMemoryFree(void *Address, SIZE_T Size, ULONG FreeType) {
|
||||
if (!Size) {
|
||||
Invalidation::InvalidateContainingSection(reinterpret_cast<uint64_t>(Address), true);
|
||||
} else if (FreeType & MEM_DECOMMIT) {
|
||||
Invalidation::InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), true);
|
||||
}
|
||||
}
|
||||
|
||||
void BTCpuNotifyUnmapViewOfSection(void *Address, ULONG Flags) {
|
||||
Invalidation::InvalidateContainingSection(reinterpret_cast<uint64_t>(Address), true);
|
||||
}
|
||||
|
||||
BOOLEAN WINAPI BTCpuIsProcessorFeaturePresent(UINT Feature) {
|
||||
switch (Feature) {
|
||||
case PF_FLOATING_POINT_PRECISION_ERRATA:
|
||||
return FALSE;
|
||||
case PF_FLOATING_POINT_EMULATED:
|
||||
return FALSE;
|
||||
case PF_COMPARE_EXCHANGE_DOUBLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_CX8);
|
||||
case PF_MMX_INSTRUCTIONS_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_MMX);
|
||||
case PF_XMMI_INSTRUCTIONS_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE);
|
||||
case PF_3DNOW_INSTRUCTIONS_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_3DNOW);
|
||||
case PF_RDTSC_INSTRUCTION_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_TSC);
|
||||
case PF_PAE_ENABLED:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_PAE);
|
||||
case PF_XMMI64_INSTRUCTIONS_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE2);
|
||||
case PF_SSE3_INSTRUCTIONS_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE3);
|
||||
case PF_SSSE3_INSTRUCTIONS_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSSE3);
|
||||
case PF_XSAVE_ENABLED:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_XSAVE);
|
||||
case PF_COMPARE_EXCHANGE128:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_CX128);
|
||||
case PF_SSE_DAZ_MODE_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_DAZ);
|
||||
case PF_NX_ENABLED:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_NX);
|
||||
case PF_SECOND_LEVEL_ADDRESS_TRANSLATION:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_2NDLEV);
|
||||
case PF_VIRT_FIRMWARE_ENABLED:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_VIRT);
|
||||
case PF_RDWRFSGSBASE_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_RDFS);
|
||||
case PF_FASTFAIL_AVAILABLE:
|
||||
return TRUE;
|
||||
case PF_SSE4_1_INSTRUCTIONS_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE41);
|
||||
case PF_SSE4_2_INSTRUCTIONS_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE42);
|
||||
case PF_AVX_INSTRUCTIONS_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_AVX);
|
||||
case PF_AVX2_INSTRUCTIONS_AVAILABLE:
|
||||
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_AVX2);
|
||||
default:
|
||||
LogMan::Msg::DFmt("Unknown CPU feature: {:X}", Feature);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
BOOLEAN BTCpuUpdateProcessorInformation(SYSTEM_CPU_INFORMATION *Info) {
|
||||
Info->ProcessorArchitecture = CpuInfo.ProcessorArchitecture;
|
||||
Info->ProcessorLevel = CpuInfo.ProcessorLevel;
|
||||
Info->ProcessorRevision = CpuInfo.ProcessorRevision;
|
||||
Info->ProcessorFeatureBits = CpuInfo.ProcessorFeatureBits;
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
LIBRARY libwow64fex.dll
|
||||
|
||||
EXPORTS
|
||||
BTCpuFlushInstructionCache2
|
||||
BTCpuGetBopCode
|
||||
BTCpuGetContext
|
||||
BTCpuIsProcessorFeaturePresent
|
||||
BTCpuNotifyMemoryAlloc
|
||||
BTCpuNotifyMemoryProtect
|
||||
BTCpuNotifyMemoryFree
|
||||
BTCpuNotifyUnmapViewOfSection
|
||||
BTCpuProcessInit
|
||||
BTCpuResetToConsistentState
|
||||
BTCpuSetContext
|
||||
BTCpuSimulate
|
||||
BTCpuSuspendLocalThread
|
||||
BTCpuThreadInit
|
||||
BTCpuThreadTerm
|
||||
BTCpuUpdateProcessorInformation
|
||||
__wine_get_unix_opcode
|
||||
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 1999 Patrik Stridvall
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <windef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int __cdecl __wine_dbg_output( const char *str );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2021 Alexandre Julliard
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <winternl.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef UINT64 unixlib_handle_t;
|
||||
|
||||
NTSTATUS WINAPI __wine_unix_call( unixlib_handle_t handle, unsigned int code, void *args );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,106 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
// SPDX-FileCopyrightText: Copyright (C) the Wine project
|
||||
|
||||
#pragma once
|
||||
|
||||
#include_next <winternl.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NtCurrentProcess() ((HANDLE)~(ULONG_PTR)0)
|
||||
|
||||
#define WOW64_TLS_MAX_NUMBER 19
|
||||
|
||||
typedef struct _THREAD_BASIC_INFORMATION
|
||||
{
|
||||
NTSTATUS ExitStatus;
|
||||
PVOID TebBaseAddress;
|
||||
CLIENT_ID ClientId;
|
||||
ULONG_PTR AffinityMask;
|
||||
LONG Priority;
|
||||
LONG BasePriority;
|
||||
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
|
||||
|
||||
/* System Information Class 0x01 */
|
||||
|
||||
typedef struct _SYSTEM_CPU_INFORMATION {
|
||||
USHORT ProcessorArchitecture;
|
||||
USHORT ProcessorLevel;
|
||||
USHORT ProcessorRevision;
|
||||
USHORT MaximumProcessors;
|
||||
ULONG ProcessorFeatureBits;
|
||||
} SYSTEM_CPU_INFORMATION, *PSYSTEM_CPU_INFORMATION;
|
||||
|
||||
/* definitions of bits in the Feature set for the x86 processors */
|
||||
#define CPU_FEATURE_VME 0x00000005 /* Virtual 86 Mode Extensions */
|
||||
#define CPU_FEATURE_TSC 0x00000002 /* Time Stamp Counter available */
|
||||
#define CPU_FEATURE_CMOV 0x00000008 /* Conditional Move instruction*/
|
||||
#define CPU_FEATURE_PGE 0x00000014 /* Page table Entry Global bit */
|
||||
#define CPU_FEATURE_PSE 0x00000024 /* Page Size Extension */
|
||||
#define CPU_FEATURE_MTRR 0x00000040 /* Memory Type Range Registers */
|
||||
#define CPU_FEATURE_CX8 0x00000080 /* Compare and eXchange 8 byte instr. */
|
||||
#define CPU_FEATURE_MMX 0x00000100 /* Multi Media eXtensions */
|
||||
#define CPU_FEATURE_X86 0x00000200 /* seems to be always ON, on the '86 */
|
||||
#define CPU_FEATURE_PAT 0x00000400 /* Page Attribute Table */
|
||||
#define CPU_FEATURE_FXSR 0x00000800 /* FXSAVE and FXSTORE instructions */
|
||||
#define CPU_FEATURE_SEP 0x00001000 /* SYSENTER and SYSEXIT instructions */
|
||||
#define CPU_FEATURE_SSE 0x00002000 /* SSE extensions (ext. MMX) */
|
||||
#define CPU_FEATURE_3DNOW 0x00004000 /* 3DNOW instructions available */
|
||||
#define CPU_FEATURE_SSE2 0x00010000 /* SSE2 extensions (XMMI64) */
|
||||
#define CPU_FEATURE_DS 0x00020000 /* Debug Store */
|
||||
#define CPU_FEATURE_HTT 0x00040000 /* Hyper Threading Technology */
|
||||
#define CPU_FEATURE_SSE3 0x00080000 /* SSE3 extensions */
|
||||
#define CPU_FEATURE_CX128 0x00100000 /* cmpxchg16b instruction */
|
||||
#define CPU_FEATURE_XSAVE 0x00800000 /* XSAVE instructions */
|
||||
#define CPU_FEATURE_2NDLEV 0x04000000 /* Second-level address translation */
|
||||
#define CPU_FEATURE_VIRT 0x08000000 /* Virtualization support */
|
||||
#define CPU_FEATURE_RDFS 0x10000000 /* RDFSBASE etc. instructions */
|
||||
#define CPU_FEATURE_NX 0x20000000 /* Data execution prevention */
|
||||
|
||||
/* FIXME: following values are made up, actual flags are unknown */
|
||||
#define CPU_FEATURE_SSSE3 0x00008000 /* SSSE3 instructions */
|
||||
#define CPU_FEATURE_SSE41 0x01000000 /* SSE41 instructions */
|
||||
#define CPU_FEATURE_SSE42 0x02000000 /* SSE42 instructions */
|
||||
#define CPU_FEATURE_AVX 0x40000000 /* AVX instructions */
|
||||
#define CPU_FEATURE_AVX2 0x80000000 /* AVX2 instructions */
|
||||
#define CPU_FEATURE_PAE 0x00200000
|
||||
#define CPU_FEATURE_DAZ 0x00400000
|
||||
|
||||
typedef enum _MEMORY_INFORMATION_CLASS {
|
||||
MemoryBasicInformation,
|
||||
MemoryWorkingSetInformation,
|
||||
MemoryMappedFilenameInformation,
|
||||
MemoryRegionInformation,
|
||||
MemoryWorkingSetExInformation,
|
||||
MemorySharedCommitInformation,
|
||||
MemoryImageInformation,
|
||||
MemoryRegionInformationEx,
|
||||
MemoryPrivilegedBasicInformation,
|
||||
MemoryEnclaveImageInformation,
|
||||
MemoryBasicInformationCapped,
|
||||
MemoryPhysicalContiguityInformation,
|
||||
MemoryBadInformation,
|
||||
MemoryBadInformationAllProcesses,
|
||||
#ifdef __WINESRC__
|
||||
MemoryWineUnixFuncs = 1000,
|
||||
MemoryWineUnixWow64Funcs,
|
||||
#endif
|
||||
} MEMORY_INFORMATION_CLASS;
|
||||
|
||||
NTSTATUS WINAPI Wow64SystemServiceEx(UINT,UINT*);
|
||||
|
||||
NTSTATUS WINAPI RtlWow64SetThreadContext(HANDLE,const WOW64_CONTEXT*);
|
||||
NTSTATUS WINAPI RtlWow64GetThreadContext(HANDLE,WOW64_CONTEXT*);
|
||||
NTSTATUS WINAPI RtlWow64GetCurrentCpuArea(USHORT*,void**,void**);
|
||||
|
||||
NTSTATUS WINAPI NtSuspendThread(HANDLE,PULONG);
|
||||
NTSTATUS WINAPI NtGetContextThread(HANDLE,CONTEXT*);
|
||||
NTSTATUS WINAPI NtContinue(PCONTEXT,BOOLEAN);
|
||||
NTSTATUS WINAPI NtQueryVirtualMemory(HANDLE,LPCVOID,MEMORY_INFORMATION_CLASS,PVOID,SIZE_T,SIZE_T*);
|
||||
NTSTATUS WINAPI NtProtectVirtualMemory(HANDLE,PVOID*,SIZE_T*,ULONG,ULONG*);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in new issue
Block a user