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
288 lines
8.6 KiB
C++
288 lines
8.6 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2020 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/base/memory.h"
|
|
|
|
#include "xenia/base/platform_win.h"
|
|
|
|
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | \
|
|
WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES)
|
|
#define XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
#endif
|
|
/*
|
|
these two dont bypass much ms garbage compared to the threading ones,
|
|
but Protect is used by PhysicalHeap::EnableAccessCallbacks which eats a lot
|
|
of cpu time, so every bit counts
|
|
*/
|
|
XE_NTDLL_IMPORT(NtProtectVirtualMemory, cls_NtProtectVirtualMemory,
|
|
NtProtectVirtualMemoryPointer);
|
|
XE_NTDLL_IMPORT(NtQueryVirtualMemory, cls_NtQueryVirtualMemory,
|
|
NtQueryVirtualMemoryPointer);
|
|
namespace xe {
|
|
namespace memory {
|
|
|
|
size_t page_size() {
|
|
#if XE_ARCH_AMD64 == 1
|
|
return 4096;
|
|
#else
|
|
static size_t value = 0;
|
|
if (!value) {
|
|
SYSTEM_INFO si;
|
|
GetSystemInfo(&si);
|
|
value = si.dwPageSize;
|
|
}
|
|
return value;
|
|
#endif
|
|
}
|
|
|
|
size_t allocation_granularity() {
|
|
#if XE_ARCH_AMD64 == 1 && XE_PLATFORM_WIN32 == 1
|
|
return 65536;
|
|
#else
|
|
static size_t value = 0;
|
|
if (!value) {
|
|
SYSTEM_INFO si;
|
|
GetSystemInfo(&si);
|
|
value = si.dwAllocationGranularity;
|
|
}
|
|
return value;
|
|
#endif
|
|
}
|
|
|
|
DWORD ToWin32ProtectFlags(PageAccess access) {
|
|
switch (access) {
|
|
case PageAccess::kNoAccess:
|
|
return PAGE_NOACCESS;
|
|
case PageAccess::kReadOnly:
|
|
return PAGE_READONLY;
|
|
case PageAccess::kReadWrite:
|
|
return PAGE_READWRITE;
|
|
case PageAccess::kExecuteReadOnly:
|
|
return PAGE_EXECUTE_READ;
|
|
case PageAccess::kExecuteReadWrite:
|
|
return PAGE_EXECUTE_READWRITE;
|
|
default:
|
|
assert_unhandled_case(access);
|
|
return PAGE_NOACCESS;
|
|
}
|
|
}
|
|
|
|
PageAccess ToXeniaProtectFlags(DWORD access) {
|
|
if (access & PAGE_GUARD) {
|
|
// Strip the page guard flag for now...
|
|
access &= ~PAGE_GUARD;
|
|
}
|
|
|
|
switch (access) {
|
|
case PAGE_NOACCESS:
|
|
return PageAccess::kNoAccess;
|
|
case PAGE_READONLY:
|
|
return PageAccess::kReadOnly;
|
|
case PAGE_READWRITE:
|
|
return PageAccess::kReadWrite;
|
|
case PAGE_EXECUTE_READ:
|
|
return PageAccess::kExecuteReadOnly;
|
|
case PAGE_EXECUTE_READWRITE:
|
|
return PageAccess::kExecuteReadWrite;
|
|
default:
|
|
return PageAccess::kNoAccess;
|
|
}
|
|
}
|
|
|
|
bool IsWritableExecutableMemorySupported() {
|
|
#ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
return true;
|
|
#else
|
|
// To test FromApp functions on desktop, undefine
|
|
// XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS and link to WindowsApp.lib.
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void* AllocFixed(void* base_address, size_t length,
|
|
AllocationType allocation_type, PageAccess access) {
|
|
DWORD alloc_type = 0;
|
|
switch (allocation_type) {
|
|
case AllocationType::kReserve:
|
|
alloc_type = MEM_RESERVE;
|
|
break;
|
|
case AllocationType::kCommit:
|
|
alloc_type = MEM_COMMIT;
|
|
break;
|
|
case AllocationType::kReserveCommit:
|
|
alloc_type = MEM_RESERVE | MEM_COMMIT;
|
|
break;
|
|
default:
|
|
assert_unhandled_case(allocation_type);
|
|
break;
|
|
}
|
|
DWORD protect = ToWin32ProtectFlags(access);
|
|
#ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
return VirtualAlloc(base_address, length, alloc_type, protect);
|
|
#else
|
|
return VirtualAllocFromApp(base_address, length, ULONG(alloc_type),
|
|
ULONG(protect));
|
|
#endif
|
|
}
|
|
|
|
bool DeallocFixed(void* base_address, size_t length,
|
|
DeallocationType deallocation_type) {
|
|
DWORD free_type = 0;
|
|
switch (deallocation_type) {
|
|
case DeallocationType::kRelease:
|
|
free_type = MEM_RELEASE;
|
|
length = 0;
|
|
break;
|
|
case DeallocationType::kDecommit:
|
|
free_type = MEM_DECOMMIT;
|
|
break;
|
|
default:
|
|
assert_unhandled_case(deallocation_type);
|
|
break;
|
|
}
|
|
return VirtualFree(base_address, length, free_type) ? true : false;
|
|
}
|
|
|
|
bool Protect(void* base_address, size_t length, PageAccess access,
|
|
PageAccess* out_old_access) {
|
|
if (out_old_access) {
|
|
*out_old_access = PageAccess::kNoAccess;
|
|
}
|
|
DWORD new_protect = ToWin32ProtectFlags(access);
|
|
|
|
#if XE_USE_NTDLL_FUNCTIONS == 1
|
|
|
|
DWORD old_protect = 0;
|
|
SIZE_T MemoryLength = length;
|
|
PVOID MemoryCache = base_address;
|
|
|
|
BOOL result = NtProtectVirtualMemoryPointer.invoke<NTSTATUS>(
|
|
(HANDLE)0xFFFFFFFFFFFFFFFFLL, &MemoryCache, &MemoryLength,
|
|
new_protect, &old_protect) >= 0;
|
|
|
|
#else
|
|
#ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
DWORD old_protect = 0;
|
|
BOOL result = VirtualProtect(base_address, length, new_protect, &old_protect);
|
|
#else
|
|
ULONG old_protect = 0;
|
|
BOOL result = VirtualProtectFromApp(base_address, length, ULONG(new_protect),
|
|
&old_protect);
|
|
#endif
|
|
#endif
|
|
if (!result) {
|
|
return false;
|
|
}
|
|
if (out_old_access) {
|
|
*out_old_access = ToXeniaProtectFlags(DWORD(old_protect));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool QueryProtect(void* base_address, size_t& length, PageAccess& access_out) {
|
|
access_out = PageAccess::kNoAccess;
|
|
|
|
MEMORY_BASIC_INFORMATION info;
|
|
ZeroMemory(&info, sizeof(info));
|
|
#if XE_USE_NTDLL_FUNCTIONS == 1
|
|
ULONG_PTR ResultLength;
|
|
|
|
NTSTATUS query_result = NtQueryVirtualMemoryPointer.invoke<NTSTATUS>(
|
|
(HANDLE)0xFFFFFFFFFFFFFFFFLL, (PVOID)base_address,
|
|
0 /* MemoryBasicInformation*/, &info, length, &ResultLength);
|
|
SIZE_T result = query_result >= 0 ? ResultLength : 0;
|
|
#else
|
|
SIZE_T result = VirtualQuery(base_address, &info, length);
|
|
|
|
#endif
|
|
if (!result) {
|
|
return false;
|
|
}
|
|
|
|
length = info.RegionSize;
|
|
access_out = ToXeniaProtectFlags(info.Protect);
|
|
return true;
|
|
}
|
|
|
|
FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path,
|
|
size_t length, PageAccess access,
|
|
bool commit) {
|
|
DWORD protect =
|
|
ToWin32ProtectFlags(access) | (commit ? SEC_COMMIT : SEC_RESERVE);
|
|
auto full_path = "Local" / path;
|
|
#ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
return CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, protect,
|
|
static_cast<DWORD>(length >> 32),
|
|
static_cast<DWORD>(length), full_path.c_str());
|
|
#else
|
|
return CreateFileMappingFromApp(INVALID_HANDLE_VALUE, nullptr, ULONG(protect),
|
|
ULONG64(length), full_path.c_str());
|
|
#endif
|
|
}
|
|
|
|
void CloseFileMappingHandle(FileMappingHandle handle,
|
|
const std::filesystem::path& path) {
|
|
CloseHandle(handle);
|
|
}
|
|
|
|
void* MapFileView(FileMappingHandle handle, void* base_address, size_t length,
|
|
PageAccess access, size_t file_offset) {
|
|
#ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
|
DWORD target_address_low = static_cast<DWORD>(file_offset);
|
|
DWORD target_address_high = static_cast<DWORD>(file_offset >> 32);
|
|
DWORD file_access = 0;
|
|
switch (access) {
|
|
case PageAccess::kReadOnly:
|
|
file_access = FILE_MAP_READ;
|
|
break;
|
|
case PageAccess::kReadWrite:
|
|
file_access = FILE_MAP_ALL_ACCESS;
|
|
break;
|
|
case PageAccess::kExecuteReadOnly:
|
|
file_access = FILE_MAP_READ | FILE_MAP_EXECUTE;
|
|
break;
|
|
case PageAccess::kExecuteReadWrite:
|
|
file_access = FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE;
|
|
break;
|
|
case PageAccess::kNoAccess:
|
|
default:
|
|
assert_unhandled_case(access);
|
|
return nullptr;
|
|
}
|
|
return MapViewOfFileEx(handle, file_access, target_address_high,
|
|
target_address_low, length, base_address);
|
|
#else
|
|
// VirtualAlloc2FromApp and MapViewOfFile3FromApp were added in 10.0.17134.0.
|
|
// https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-apis
|
|
HANDLE process = GetCurrentProcess();
|
|
void* placeholder = VirtualAlloc2FromApp(
|
|
process, base_address, length, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
|
|
PAGE_NOACCESS, nullptr, 0);
|
|
if (!placeholder) {
|
|
return nullptr;
|
|
}
|
|
void* mapping = MapViewOfFile3FromApp(
|
|
handle, process, placeholder, ULONG64(file_offset), length,
|
|
MEM_REPLACE_PLACEHOLDER, ULONG(ToWin32ProtectFlags(access)), nullptr, 0);
|
|
if (!mapping) {
|
|
VirtualFree(placeholder, length, MEM_RELEASE);
|
|
return nullptr;
|
|
}
|
|
return mapping;
|
|
#endif
|
|
}
|
|
|
|
bool UnmapFileView(FileMappingHandle handle, void* base_address,
|
|
size_t length) {
|
|
return UnmapViewOfFile(base_address) ? true : false;
|
|
}
|
|
|
|
} // namespace memory
|
|
} // namespace xe
|