Files
FEX-Emu--FEX/Source/Tools/LinuxEmulation/LinuxSyscalls/FaultSafeUserMemAccess.cpp
T
crueter 9e8463d6d7 [cmake] refactor: compiler and architecture handling
- Do compiler/architecture checks EARLY, don't waste time doing random
  configuration stuff if the user can't even compile in the first place
- MSVC is unsupported, I assume? So add a check to disallow. There's
  literally no MSVC or MSC_VER checks anywhere, so...
- Rather than using the MSVC architecture definitions, use our own
  `ARCHITECTURE_arm64` et al. Hijacking existing "standard" definitions
  is a very bad idea. Also makes it more readable in CMake
- Change the x86 host check to `x86|amd64`. Some systems still refer to
  themselves as x86 despite being 64-bit for... reasons, and I saw one a
  very long time ago that referred to it as amd64. This should
  basically never come up, nor is it really relevant given that FEX is
  for arm64... but it kinda annoyed me so whatever.

TODOs:
- Should we check `CMAKE_SIZEOF_VOID_P (equal) 64`? I don't think anyone
  is even trying to compile this thing on armv7 or older, but might as
  well? maybe?
- What's the status of *BSD, Solaris, macOS? Technically macOS does
  support Wine, not sure about the others.

Signed-off-by: crueter <crueter@eden-emu.dev>
2025-12-29 14:05:09 -05:00

184 lines
4.6 KiB
C++

// SPDX-License-Identifier: MIT
#include "LinuxSyscalls/Syscalls.h"
namespace FEX::HLE::FaultSafeUserMemAccess {
#ifdef ARCHITECTURE_arm64
__attribute__((naked)) size_t CopyFromUser(void* Dest, const void* Src, size_t Size) {
__asm volatile(R"(
// Early exit if a memcpy of size zero.
cbz x2, 2f;
1:
.globl CopyFromUser_FaultInst
CopyFromUser_FaultInst:
ldrb w3, [x1], 1; // <- This line can fault.
strb w3, [x0], 1;
sub x2, x2, 1;
cbnz x2, 1b;
2:
mov x0, 0;
ret;
)" ::
: "memory");
}
__attribute__((naked)) size_t CopyToUser(void* Dest, const void* Src, size_t Size) {
__asm volatile(R"(
// Early exit if a memcpy of size zero.
cbz x2, 2f;
1:
ldrb w3, [x1], 1;
.globl CopyToUser_FaultInst
CopyToUser_FaultInst:
strb w3, [x0], 1; // <- This line can fault.
sub x2, x2, 1;
cbnz x2, 1b;
2:
mov x0, 0;
ret;
)" ::
: "memory");
}
extern "C" uint64_t CopyFromUser_FaultInst;
void* const CopyFromUser_FaultLocation = &CopyFromUser_FaultInst;
extern "C" uint64_t CopyToUser_FaultInst;
void* const CopyToUser_FaultLocation = &CopyToUser_FaultInst;
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED && defined(ARCHITECTURE_arm64)
__attribute__((naked)) bool VerifyIsReadableImpl(const void* Src, size_t Size) {
__asm volatile(R"(
// Early exit if size is zero.
cbz x1, 2f;
1:
.globl UserReadable_FaultInst
UserReadable_FaultInst:
ldrb wzr, [x0], 1; // <- This line can fault.
sub x1, x1, 1;
cbnz x1, 1b;
2:
mov x0, 1;
ret;
)" ::
: "memory");
}
__attribute__((naked)) bool VerifyIsOnlyWritable(void* Src, size_t Size) {
__asm volatile(R"(
// Early exit if size is zero.
cbz x1, 2f;
1:
ldrb w2, [x0];
.globl UserWritable_FaultInst
UserWritable_FaultInst:
strb w2, [x0], 1; // <- This line can fault.
sub x1, x1, 1;
cbnz x1, 1b;
2:
mov x0, 1;
ret;
)" ::
: "memory");
}
__attribute__((naked)) bool VerifyIsStringReadableMaxSizeImpl(const char* Src, size_t MaxSize) {
__asm volatile(R"(
1:
cbz x1, 2f;
.globl UserStringReadable_FaultInst
UserStringReadable_FaultInst:
ldrb w2, [x0], 1; //< This line can fault.
sub x1, x1, 1;
cbnz x2, 1b;
2:
mov x0, 1;
ret;
)" ::
: "memory");
}
void VerifyIsReadable(const void* Src, size_t Size) {
LOGMAN_THROW_A_FMT(VerifyIsReadableImpl(Src, Size), "EFAULT needs readable!");
}
void VerifyIsStringReadable(const char* Src) {
LOGMAN_THROW_A_FMT(VerifyIsStringReadableMaxSizeImpl(Src, ~0ULL), "EFAULT needs string readable!");
}
void VerifyIsStringReadableMaxSize(const char* Src, size_t MaxSize) {
LOGMAN_THROW_A_FMT(VerifyIsStringReadableMaxSizeImpl(Src, MaxSize), "EFAULT needs string readable!");
}
void VerifyIsReadableOrNull(const void* Src, size_t Size) {
if (Src == nullptr) {
return;
}
LOGMAN_THROW_A_FMT(VerifyIsReadableImpl(Src, Size), "EFAULT needs readable!");
}
void VerifyIsWritable(void* Src, size_t Size) {
///< Checking if writable needs to check if readable first.
VerifyIsReadable(Src, Size);
LOGMAN_THROW_A_FMT(VerifyIsOnlyWritable(Src, Size), "EFAULT needs writable!");
}
void VerifyIsWritableOrNull(void* Src, size_t Size) {
if (Src == nullptr) {
return;
}
///< Checking if writable needs to check if readable first.
VerifyIsReadable(Src, Size);
LOGMAN_THROW_A_FMT(VerifyIsOnlyWritable(Src, Size), "EFAULT needs writable!");
}
extern "C" uint64_t UserReadable_FaultInst;
void* const UserReadable_FaultLocation = &UserReadable_FaultInst;
extern "C" uint64_t UserWritable_FaultInst;
void* const UserWritable_FaultLocation = &UserWritable_FaultInst;
extern "C" uint64_t UserStringReadable_FaultInst;
void* const UserStringReadable_FaultLocation = &UserStringReadable_FaultInst;
#endif
bool IsFaultLocation(uint64_t PC) {
bool IsMemcpyFault = false;
IsMemcpyFault |= reinterpret_cast<void*>(PC) == CopyToUser_FaultLocation;
IsMemcpyFault |= reinterpret_cast<void*>(PC) == CopyFromUser_FaultLocation;
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED && defined(ARCHITECTURE_arm64)
IsMemcpyFault |= reinterpret_cast<void*>(PC) == UserReadable_FaultLocation;
IsMemcpyFault |= reinterpret_cast<void*>(PC) == UserWritable_FaultLocation;
IsMemcpyFault |= reinterpret_cast<void*>(PC) == UserStringReadable_FaultLocation;
#endif
return IsMemcpyFault;
}
#else
size_t CopyFromUser(void* Dest, const void* Src, size_t Size) {
memcpy(Dest, Src, Size);
return Size;
}
size_t CopyToUser(void* Dest, const void* Src, size_t Size) {
memcpy(Dest, Src, Size);
return Size;
}
bool IsFaultLocation(uint64_t PC) {
return false;
}
#endif
} // namespace FEX::HLE::FaultSafeUserMemAccess