aboutsummaryrefslogtreecommitdiff
path: root/client/asmjit/core/virtmem.cpp
diff options
context:
space:
mode:
authorauth12 <[email protected]>2020-07-19 11:45:43 -0700
committerauth12 <[email protected]>2020-07-19 11:45:43 -0700
commit4e6a09d486ed462ee4cf38c3735a12d530dc09d4 (patch)
treea67ccac41fef7a412b4357fbe54582cc4b692863 /client/asmjit/core/virtmem.cpp
parentDeleted asmjit submodule (diff)
downloadloader-4e6a09d486ed462ee4cf38c3735a12d530dc09d4.tar.xz
loader-4e6a09d486ed462ee4cf38c3735a12d530dc09d4.zip
Added asmjit.
Fixed solution file.
Diffstat (limited to 'client/asmjit/core/virtmem.cpp')
-rw-r--r--client/asmjit/core/virtmem.cpp589
1 files changed, 589 insertions, 0 deletions
diff --git a/client/asmjit/core/virtmem.cpp b/client/asmjit/core/virtmem.cpp
new file mode 100644
index 0000000..0606748
--- /dev/null
+++ b/client/asmjit/core/virtmem.cpp
@@ -0,0 +1,589 @@
+// AsmJit - Machine code generation for C++
+//
+// * Official AsmJit Home Page: https://asmjit.com
+// * Official Github Repository: https://github.com/asmjit/asmjit
+//
+// Copyright (c) 2008-2020 The AsmJit Authors
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+#include "../core/api-build_p.h"
+#ifndef ASMJIT_NO_JIT
+
+#include "../core/osutils.h"
+#include "../core/string.h"
+#include "../core/support.h"
+#include "../core/virtmem.h"
+
+#if !defined(_WIN32)
+ #include <errno.h>
+ #include <fcntl.h>
+ #include <sys/mman.h>
+ #include <sys/stat.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+
+ // Linux has a `memfd_create` syscall that we would like to use, if available.
+ #if defined(__linux__)
+ #include <sys/syscall.h>
+ #endif
+
+ // Apple recently introduced MAP_JIT flag, which we want to use.
+ #if defined(__APPLE__)
+ #include <TargetConditionals.h>
+ #if TARGET_OS_OSX
+ #include <sys/utsname.h>
+ #endif
+ // Older SDK doesn't define `MAP_JIT`.
+ #ifndef MAP_JIT
+ #define MAP_JIT 0x800
+ #endif
+ #endif
+
+ // BSD/OSX: `MAP_ANONYMOUS` is not defined, `MAP_ANON` is.
+ #if !defined(MAP_ANONYMOUS)
+ #define MAP_ANONYMOUS MAP_ANON
+ #endif
+#endif
+
+#include <atomic>
+
+#if defined(__APPLE__)
+ #define ASMJIT_VM_SHM_DETECT 0
+#else
+ #define ASMJIT_VM_SHM_DETECT 1
+#endif
+
+ASMJIT_BEGIN_NAMESPACE
+
+// ============================================================================
+// [asmjit::VirtMem - Utilities]
+// ============================================================================
+
+static const uint32_t VirtMem_dualMappingFilter[2] = {
+ VirtMem::kAccessWrite,
+ VirtMem::kAccessExecute
+};
+
+// ============================================================================
+// [asmjit::VirtMem - Virtual Memory [Windows]]
+// ============================================================================
+
+#if defined(_WIN32)
+struct ScopedHandle {
+ inline ScopedHandle() noexcept
+ : value(nullptr) {}
+
+ inline ~ScopedHandle() noexcept {
+ if (value != nullptr)
+ ::CloseHandle(value);
+ }
+
+ HANDLE value;
+};
+
+static void VirtMem_getInfo(VirtMem::Info& vmInfo) noexcept {
+ SYSTEM_INFO systemInfo;
+
+ ::GetSystemInfo(&systemInfo);
+ vmInfo.pageSize = Support::alignUpPowerOf2<uint32_t>(systemInfo.dwPageSize);
+ vmInfo.pageGranularity = systemInfo.dwAllocationGranularity;
+}
+
+// Windows specific implementation that uses `VirtualAlloc` and `VirtualFree`.
+static DWORD VirtMem_accessToWinProtectFlags(uint32_t flags) noexcept {
+ DWORD protectFlags;
+
+ // READ|WRITE|EXECUTE.
+ if (flags & VirtMem::kAccessExecute)
+ protectFlags = (flags & VirtMem::kAccessWrite) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
+ else if (flags & VirtMem::kAccessReadWrite)
+ protectFlags = (flags & VirtMem::kAccessWrite) ? PAGE_READWRITE : PAGE_READONLY;
+ else
+ protectFlags = PAGE_NOACCESS;
+
+ // Any other flags to consider?
+ return protectFlags;
+}
+
+static DWORD VirtMem_accessToWinDesiredAccess(uint32_t flags) noexcept {
+ DWORD access = (flags & VirtMem::kAccessWrite) ? FILE_MAP_WRITE : FILE_MAP_READ;
+ if (flags & VirtMem::kAccessExecute)
+ access |= FILE_MAP_EXECUTE;
+ return access;
+}
+
+Error VirtMem::alloc(void** p, size_t size, uint32_t flags) noexcept {
+ *p = nullptr;
+ if (size == 0)
+ return DebugUtils::errored(kErrorInvalidArgument);
+
+ DWORD protectFlags = VirtMem_accessToWinProtectFlags(flags);
+ void* result = ::VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, protectFlags);
+
+ if (!result)
+ return DebugUtils::errored(kErrorOutOfMemory);
+
+ *p = result;
+ return kErrorOk;
+}
+
+Error VirtMem::release(void* p, size_t size) noexcept {
+ DebugUtils::unused(size);
+ if (ASMJIT_UNLIKELY(!::VirtualFree(p, 0, MEM_RELEASE)))
+ return DebugUtils::errored(kErrorInvalidArgument);
+ return kErrorOk;
+}
+
+Error VirtMem::protect(void* p, size_t size, uint32_t flags) noexcept {
+ DWORD protectFlags = VirtMem_accessToWinProtectFlags(flags);
+ DWORD oldFlags;
+
+ if (::VirtualProtect(p, size, protectFlags, &oldFlags))
+ return kErrorOk;
+
+ return DebugUtils::errored(kErrorInvalidArgument);
+}
+
+Error VirtMem::allocDualMapping(DualMapping* dm, size_t size, uint32_t flags) noexcept {
+ dm->ro = nullptr;
+ dm->rw = nullptr;
+
+ if (size == 0)
+ return DebugUtils::errored(kErrorInvalidArgument);
+
+ ScopedHandle handle;
+ handle.value = ::CreateFileMappingW(
+ INVALID_HANDLE_VALUE,
+ nullptr,
+ PAGE_EXECUTE_READWRITE,
+ (DWORD)(uint64_t(size) >> 32),
+ (DWORD)(size & 0xFFFFFFFFu),
+ nullptr);
+
+ if (ASMJIT_UNLIKELY(!handle.value))
+ return DebugUtils::errored(kErrorOutOfMemory);
+
+ void* ptr[2];
+ for (uint32_t i = 0; i < 2; i++) {
+ DWORD desiredAccess = VirtMem_accessToWinDesiredAccess(flags & ~VirtMem_dualMappingFilter[i]);
+ ptr[i] = ::MapViewOfFile(handle.value, desiredAccess, 0, 0, size);
+
+ if (ptr[i] == nullptr) {
+ if (i == 0)
+ ::UnmapViewOfFile(ptr[0]);
+ return DebugUtils::errored(kErrorOutOfMemory);
+ }
+ }
+
+ dm->ro = ptr[0];
+ dm->rw = ptr[1];
+ return kErrorOk;
+}
+
+Error VirtMem::releaseDualMapping(DualMapping* dm, size_t size) noexcept {
+ DebugUtils::unused(size);
+ bool failed = false;
+
+ if (!::UnmapViewOfFile(dm->ro))
+ failed = true;
+
+ if (dm->ro != dm->rw && !UnmapViewOfFile(dm->rw))
+ failed = true;
+
+ if (failed)
+ return DebugUtils::errored(kErrorInvalidArgument);
+
+ dm->ro = nullptr;
+ dm->rw = nullptr;
+ return kErrorOk;
+}
+#endif
+
+// ============================================================================
+// [asmjit::VirtMem - Virtual Memory [Posix]]
+// ============================================================================
+
+#if !defined(_WIN32)
+struct ScopedFD {
+ inline ScopedFD() noexcept
+ : value(-1) {}
+
+ inline ~ScopedFD() noexcept {
+ if (value != -1)
+ close(value);
+ }
+
+ int value;
+};
+
+static void VirtMem_getInfo(VirtMem::Info& vmInfo) noexcept {
+ uint32_t pageSize = uint32_t(::getpagesize());
+
+ vmInfo.pageSize = pageSize;
+ vmInfo.pageGranularity = Support::max<uint32_t>(pageSize, 65536);
+}
+
+// Some operating systems don't allow /dev/shm to be executable. On Linux this
+// happens when /dev/shm is mounted with 'noexec', which is enforced by systemd.
+// Other operating systems like OSX also restrict executable permissions regarding
+// /dev/shm, so we use a runtime detection before trying to allocate the requested
+// memory by the user. Sometimes we don't need the detection as we know it would
+// always result in 'kShmStrategyTmpDir'.
+enum ShmStrategy : uint32_t {
+ kShmStrategyUnknown = 0,
+ kShmStrategyDevShm = 1,
+ kShmStrategyTmpDir = 2
+};
+
+// Posix specific implementation that uses `mmap()` and `munmap()`.
+static int VirtMem_accessToPosixProtection(uint32_t flags) noexcept {
+ int protection = 0;
+ if (flags & VirtMem::kAccessRead ) protection |= PROT_READ;
+ if (flags & VirtMem::kAccessWrite ) protection |= PROT_READ | PROT_WRITE;
+ if (flags & VirtMem::kAccessExecute) protection |= PROT_READ | PROT_EXEC;
+ return protection;
+}
+
+// Translates libc errors specific to VirtualMemory mapping to `asmjit::Error`.
+static Error VirtMem_makeErrorFromErrno(int e) noexcept {
+ switch (e) {
+ case EACCES:
+ case EAGAIN:
+ case ENODEV:
+ case EPERM:
+ return kErrorInvalidState;
+
+ case EFBIG:
+ case ENOMEM:
+ case EOVERFLOW:
+ return kErrorOutOfMemory;
+
+ case EMFILE:
+ case ENFILE:
+ return kErrorTooManyHandles;
+
+ default:
+ return kErrorInvalidArgument;
+ }
+}
+
+#if defined(__APPLE__)
+// Detects whether the current process is hardened, which means that pages that
+// have WRITE and EXECUTABLE flags cannot be allocated without MAP_JIT flag.
+static ASMJIT_INLINE bool VirtMem_isHardened() noexcept {
+ static volatile uint32_t globalHardenedFlag;
+
+ enum HardenedFlag : uint32_t {
+ kHardenedFlagUnknown = 0,
+ kHardenedFlagDisabled = 1,
+ kHardenedFlagEnabled = 2
+ };
+
+ uint32_t flag = globalHardenedFlag;
+ if (flag == kHardenedFlagUnknown) {
+ VirtMem::Info memInfo;
+ VirtMem_getInfo(memInfo);
+
+ void* ptr = mmap(nullptr, memInfo.pageSize, PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (ptr == MAP_FAILED) {
+ flag = kHardenedFlagEnabled;
+ }
+ else {
+ flag = kHardenedFlagDisabled;
+ munmap(ptr, memInfo.pageSize);
+ }
+ globalHardenedFlag = flag;
+ }
+
+ return flag == kHardenedFlagEnabled;
+}
+
+// MAP_JIT flag required to run unsigned JIT code is only supported by kernel
+// version 10.14+ (Mojave) and IOS.
+static ASMJIT_INLINE bool VirtMem_hasMapJitSupport() noexcept {
+#if TARGET_OS_OSX
+ static volatile int globalVersion;
+
+ int ver = globalVersion;
+ if (!ver) {
+ struct utsname osname;
+ uname(&osname);
+ ver = atoi(osname.release);
+ globalVersion = ver;
+ }
+ return ver >= 18;
+#else
+ // Assume it's available.
+ return true;
+#endif
+}
+
+static ASMJIT_INLINE int VirtMem_appleSpecificMMapFlags(uint32_t flags) {
+ // Always use MAP_JIT flag if user asked for it (could be used for testing
+ // on non-hardened processes) and detect whether it must be used when the
+ // process is actually hardened (in that case it doesn't make sense to rely
+ // on user `flags`).
+ bool useMapJit = ((flags & VirtMem::kMMapEnableMapJit) != 0) || VirtMem_isHardened();
+ if (useMapJit)
+ return VirtMem_hasMapJitSupport() ? int(MAP_JIT) : 0;
+ else
+ return 0;
+}
+#else
+static ASMJIT_INLINE int VirtMem_appleSpecificMMapFlags(uint32_t flags) {
+ DebugUtils::unused(flags);
+ return 0;
+}
+#endif
+
+static const char* VirtMem_getTmpDir() noexcept {
+ const char* tmpDir = getenv("TMPDIR");
+ return tmpDir ? tmpDir : "/tmp";
+}
+
+static Error VirtMem_openAnonymousMemory(int* fd, bool preferTmpOverDevShm) noexcept {
+#if defined(SYS_memfd_create)
+ // Linux specific 'memfd_create' - if the syscall returns `ENOSYS` it means
+ // it's not available and we will never call it again (would be pointless).
+
+ // Zero initialized, if ever changed to '1' that would mean the syscall is not
+ // available and we must use `shm_open()` and `shm_unlink()`.
+ static volatile uint32_t memfd_create_not_supported;
+
+ if (!memfd_create_not_supported) {
+ *fd = (int)syscall(SYS_memfd_create, "vmem", 0);
+ if (ASMJIT_LIKELY(*fd >= 0))
+ return kErrorOk;
+
+ int e = errno;
+ if (e == ENOSYS)
+ memfd_create_not_supported = 1;
+ else
+ return DebugUtils::errored(VirtMem_makeErrorFromErrno(e));
+ }
+#endif
+
+#if defined(SHM_ANON)
+ // Originally FreeBSD extension, apparently works in other BSDs too.
+ DebugUtils::unused(preferTmpOverDevShm);
+ *fd = shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+
+ if (ASMJIT_LIKELY(*fd >= 0))
+ return kErrorOk;
+ else
+ return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno));
+#else
+ // POSIX API. We have to generate somehow a unique name. This is nothing
+ // cryptographic, just using a bit from the stack address to always have
+ // a different base for different threads (as threads have their own stack)
+ // and retries for avoiding collisions. We use `shm_open()` with flags that
+ // require creation of the file so we never open an existing shared memory.
+ static std::atomic<uint32_t> internalCounter;
+
+ StringTmp<128> uniqueName;
+ const char* kShmFormat = "/shm-id-%08llX";
+
+ uint32_t kRetryCount = 100;
+ uint64_t bits = ((uintptr_t)(void*)&uniqueName) & 0x55555555u;
+
+ for (uint32_t i = 0; i < kRetryCount; i++) {
+ bits -= uint64_t(OSUtils::getTickCount()) * 773703683;
+ bits = ((bits >> 14) ^ (bits << 6)) + uint64_t(++internalCounter) * 10619863;
+
+ if (!ASMJIT_VM_SHM_DETECT || preferTmpOverDevShm) {
+ uniqueName.assign(VirtMem_getTmpDir());
+ uniqueName.appendFormat(kShmFormat, (unsigned long long)bits);
+ *fd = open(uniqueName.data(), O_RDWR | O_CREAT | O_EXCL, 0);
+ if (ASMJIT_LIKELY(*fd >= 0)) {
+ unlink(uniqueName.data());
+ return kErrorOk;
+ }
+ }
+ else {
+ uniqueName.assignFormat(kShmFormat, (unsigned long long)bits);
+ *fd = shm_open(uniqueName.data(), O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+ if (ASMJIT_LIKELY(*fd >= 0)) {
+ shm_unlink(uniqueName.data());
+ return kErrorOk;
+ }
+ }
+
+ int e = errno;
+ if (e == EEXIST)
+ continue;
+ else
+ return DebugUtils::errored(VirtMem_makeErrorFromErrno(e));
+ }
+ return kErrorOk;
+#endif
+}
+
+#if ASMJIT_VM_SHM_DETECT
+static Error VirtMem_detectShmStrategy(uint32_t* strategyOut) noexcept {
+ ScopedFD fd;
+ VirtMem::Info vmInfo = VirtMem::info();
+
+ ASMJIT_PROPAGATE(VirtMem_openAnonymousMemory(&fd.value, false));
+ if (ftruncate(fd.value, off_t(vmInfo.pageSize)) != 0)
+ return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno));
+
+ void* ptr = mmap(nullptr, vmInfo.pageSize, PROT_READ | PROT_EXEC, MAP_SHARED, fd.value, 0);
+ if (ptr == MAP_FAILED) {
+ int e = errno;
+ if (e == EINVAL) {
+ *strategyOut = kShmStrategyTmpDir;
+ return kErrorOk;
+ }
+ return DebugUtils::errored(VirtMem_makeErrorFromErrno(e));
+ }
+ else {
+ munmap(ptr, vmInfo.pageSize);
+ *strategyOut = kShmStrategyDevShm;
+ return kErrorOk;
+ }
+}
+#endif
+
+#if ASMJIT_VM_SHM_DETECT
+static Error VirtMem_getShmStrategy(uint32_t* strategyOut) noexcept {
+ // Initially don't assume anything. It has to be tested whether
+ // '/dev/shm' was mounted with 'noexec' flag or not.
+ static volatile uint32_t globalShmStrategy = kShmStrategyUnknown;
+
+ uint32_t strategy = globalShmStrategy;
+ if (strategy == kShmStrategyUnknown) {
+ ASMJIT_PROPAGATE(VirtMem_detectShmStrategy(&strategy));
+ globalShmStrategy = strategy;
+ }
+
+ *strategyOut = strategy;
+ return kErrorOk;
+}
+#else
+static Error VirtMem_getShmStrategy(uint32_t* strategyOut) noexcept {
+ *strategyOut = kShmStrategyTmpDir;
+ return kErrorOk;
+}
+#endif
+
+Error VirtMem::alloc(void** p, size_t size, uint32_t flags) noexcept {
+ *p = nullptr;
+
+ if (size == 0)
+ return DebugUtils::errored(kErrorInvalidArgument);
+
+ int protection = VirtMem_accessToPosixProtection(flags);
+ int mmFlags = MAP_PRIVATE | MAP_ANONYMOUS | VirtMem_appleSpecificMMapFlags(flags);
+ void* ptr = mmap(nullptr, size, protection, mmFlags, -1, 0);
+
+ if (ptr == MAP_FAILED)
+ return DebugUtils::errored(kErrorOutOfMemory);
+
+ *p = ptr;
+ return kErrorOk;
+}
+
+Error VirtMem::release(void* p, size_t size) noexcept {
+ if (ASMJIT_UNLIKELY(munmap(p, size) != 0))
+ return DebugUtils::errored(kErrorInvalidArgument);
+
+ return kErrorOk;
+}
+
+
+Error VirtMem::protect(void* p, size_t size, uint32_t flags) noexcept {
+ int protection = VirtMem_accessToPosixProtection(flags);
+ if (mprotect(p, size, protection) == 0)
+ return kErrorOk;
+
+ return DebugUtils::errored(kErrorInvalidArgument);
+}
+
+Error VirtMem::allocDualMapping(DualMapping* dm, size_t size, uint32_t flags) noexcept {
+ dm->ro = nullptr;
+ dm->rw = nullptr;
+
+ if (off_t(size) <= 0)
+ return DebugUtils::errored(size == 0 ? kErrorInvalidArgument : kErrorTooLarge);
+
+ bool preferTmpOverDevShm = (flags & kMappingPreferTmp) != 0;
+ if (!preferTmpOverDevShm) {
+ uint32_t strategy;
+ ASMJIT_PROPAGATE(VirtMem_getShmStrategy(&strategy));
+ preferTmpOverDevShm = (strategy == kShmStrategyTmpDir);
+ }
+
+ // ScopedFD will automatically close the file descriptor in its destructor.
+ ScopedFD fd;
+ ASMJIT_PROPAGATE(VirtMem_openAnonymousMemory(&fd.value, preferTmpOverDevShm));
+ if (ftruncate(fd.value, off_t(size)) != 0)
+ return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno));
+
+ void* ptr[2];
+ for (uint32_t i = 0; i < 2; i++) {
+ ptr[i] = mmap(nullptr, size, VirtMem_accessToPosixProtection(flags & ~VirtMem_dualMappingFilter[i]), MAP_SHARED, fd.value, 0);
+ if (ptr[i] == MAP_FAILED) {
+ // Get the error now before `munmap` has a chance to clobber it.
+ int e = errno;
+ if (i == 1)
+ munmap(ptr[0], size);
+ return DebugUtils::errored(VirtMem_makeErrorFromErrno(e));
+ }
+ }
+
+ dm->ro = ptr[0];
+ dm->rw = ptr[1];
+ return kErrorOk;
+}
+
+Error VirtMem::releaseDualMapping(DualMapping* dm, size_t size) noexcept {
+ Error err = release(dm->ro, size);
+ if (dm->ro != dm->rw)
+ err |= release(dm->rw, size);
+
+ if (err)
+ return DebugUtils::errored(kErrorInvalidArgument);
+
+ dm->ro = nullptr;
+ dm->rw = nullptr;
+ return kErrorOk;
+}
+#endif
+
+// ============================================================================
+// [asmjit::VirtMem - Virtual Memory [Memory Info]]
+// ============================================================================
+
+VirtMem::Info VirtMem::info() noexcept {
+ static VirtMem::Info vmInfo;
+ static std::atomic<uint32_t> vmInfoInitialized;
+
+ if (!vmInfoInitialized.load()) {
+ VirtMem::Info localMemInfo;
+ VirtMem_getInfo(localMemInfo);
+
+ vmInfo = localMemInfo;
+ vmInfoInitialized.store(1u);
+ }
+
+ return vmInfo;
+}
+
+ASMJIT_END_NAMESPACE
+
+#endif