From 8b35b4cffb62ecb58a903bf91cb7537d7a672211 Mon Sep 17 00:00:00 2001 From: kobalicek Date: Sat, 6 Mar 2021 09:05:42 +0100 Subject: [PATCH] Added support for PROT_MAX() in VirtMem::alloc() and refactored VirtMem a bit --- src/asmjit/core/jitallocator.cpp | 4 +- src/asmjit/core/virtmem.cpp | 369 ++++++++++++++++--------------- src/asmjit/core/virtmem.h | 52 +++-- 3 files changed, 232 insertions(+), 193 deletions(-) diff --git a/src/asmjit/core/jitallocator.cpp b/src/asmjit/core/jitallocator.cpp index 848ff45..b576e21 100644 --- a/src/asmjit/core/jitallocator.cpp +++ b/src/asmjit/core/jitallocator.cpp @@ -534,11 +534,11 @@ static JitAllocatorBlock* JitAllocatorImpl_newBlock(JitAllocatorPrivateImpl* imp uint32_t blockFlags = 0; if (bitWords != nullptr) { if (impl->options & JitAllocator::kOptionUseDualMapping) { - err = VirtMem::allocDualMapping(&virtMem, blockSize, VirtMem::kAccessReadWrite | VirtMem::kAccessExecute); + err = VirtMem::allocDualMapping(&virtMem, blockSize, VirtMem::kAccessRWX); blockFlags |= JitAllocatorBlock::kFlagDualMapped; } else { - err = VirtMem::alloc(&virtMem.ro, blockSize, VirtMem::kAccessReadWrite | VirtMem::kAccessExecute); + err = VirtMem::alloc(&virtMem.ro, blockSize, VirtMem::kAccessRWX); virtMem.rw = virtMem.ro; } } diff --git a/src/asmjit/core/virtmem.cpp b/src/asmjit/core/virtmem.cpp index a1b0f95..b3f98b4 100644 --- a/src/asmjit/core/virtmem.cpp +++ b/src/asmjit/core/virtmem.cpp @@ -75,8 +75,8 @@ ASMJIT_BEGIN_NAMESPACE // ============================================================================ static const uint32_t VirtMem_dualMappingFilter[2] = { - VirtMem::kAccessWrite, - VirtMem::kAccessExecute + VirtMem::kAccessWrite | VirtMem::kMMapMaxAccessWrite, + VirtMem::kAccessExecute | VirtMem::kMMapMaxAccessExecute }; // ============================================================================ @@ -104,14 +104,14 @@ static void VirtMem_getInfo(VirtMem::Info& vmInfo) noexcept { vmInfo.pageGranularity = systemInfo.dwAllocationGranularity; } -// Windows specific implementation that uses `VirtualAlloc` and `VirtualFree`. -static DWORD VirtMem_accessToWinProtectFlags(uint32_t flags) noexcept { +// Returns windows-specific protectFlags from \ref VirtMem::Flags. +static DWORD VirtMem_winProtectFlagsFromFlags(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) + else if (flags & VirtMem::kAccessRW) protectFlags = (flags & VirtMem::kAccessWrite) ? PAGE_READWRITE : PAGE_READONLY; else protectFlags = PAGE_NOACCESS; @@ -120,7 +120,7 @@ static DWORD VirtMem_accessToWinProtectFlags(uint32_t flags) noexcept { return protectFlags; } -static DWORD VirtMem_accessToWinDesiredAccess(uint32_t flags) noexcept { +static DWORD VirtMem_winDesiredAccessFromFlags(uint32_t flags) noexcept { DWORD access = (flags & VirtMem::kAccessWrite) ? FILE_MAP_WRITE : FILE_MAP_READ; if (flags & VirtMem::kAccessExecute) access |= FILE_MAP_EXECUTE; @@ -132,7 +132,7 @@ Error VirtMem::alloc(void** p, size_t size, uint32_t flags) noexcept { if (size == 0) return DebugUtils::errored(kErrorInvalidArgument); - DWORD protectFlags = VirtMem_accessToWinProtectFlags(flags); + DWORD protectFlags = VirtMem_winProtectFlagsFromFlags(flags); void* result = ::VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, protectFlags); if (!result) @@ -150,7 +150,7 @@ Error VirtMem::release(void* p, size_t size) noexcept { } Error VirtMem::protect(void* p, size_t size, uint32_t flags) noexcept { - DWORD protectFlags = VirtMem_accessToWinProtectFlags(flags); + DWORD protectFlags = VirtMem_winProtectFlagsFromFlags(flags); DWORD oldFlags; if (::VirtualProtect(p, size, protectFlags, &oldFlags)) @@ -180,7 +180,8 @@ Error VirtMem::allocDualMapping(DualMapping* dm, size_t size, uint32_t flags) no void* ptr[2]; for (uint32_t i = 0; i < 2; i++) { - DWORD desiredAccess = VirtMem_accessToWinDesiredAccess(flags & ~VirtMem_dualMappingFilter[i]); + uint32_t accessFlags = flags & ~VirtMem_dualMappingFilter[i]; + DWORD desiredAccess = VirtMem_winDesiredAccessFromFlags(accessFlags); ptr[i] = ::MapViewOfFile(handle.value, desiredAccess, 0, 0, size); if (ptr[i] == nullptr) { @@ -219,51 +220,6 @@ Error VirtMem::releaseDualMapping(DualMapping* dm, size_t size) noexcept { // ============================================================================ #if !defined(_WIN32) -class AnonymousMemory { -public: - enum FileType : uint32_t { - kFileTypeNone, - kFileTypeTmp, - kFileTypeShm - }; - - int fd; - FileType fileType; - StringTmp<128> tmpName; - - AnonymousMemory() noexcept - : fd(-1), - fileType(kFileTypeNone), - tmpName() {} - - ~AnonymousMemory() noexcept { - unlinkFile(); - closeFile(); - } - - int closeFile() noexcept { - if (fd >= 0) { - int result = close(fd); - fd = -1; - return result; - } - else { - return 0; - } - } - - int unlinkFile() noexcept { - FileType type = fileType; - fileType = kFileTypeNone; - - if (type == kFileTypeTmp) - return unlink(tmpName.data()); - else if (type == kFileTypeShm) - return shm_unlink(tmpName.data()); - else - return 0; - } -}; static void VirtMem_getInfo(VirtMem::Info& vmInfo) noexcept { uint32_t pageSize = uint32_t(::getpagesize()); @@ -272,29 +228,15 @@ static void VirtMem_getInfo(VirtMem::Info& vmInfo) noexcept { vmInfo.pageGranularity = Support::max(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; +#if !defined(SHM_ANON) +static const char* VirtMem_getTmpDir() noexcept { + const char* tmpDir = getenv("TMPDIR"); + return tmpDir ? tmpDir : "/tmp"; } +#endif // Translates libc errors specific to VirtualMemory mapping to `asmjit::Error`. -static Error VirtMem_makeErrorFromErrno(int e) noexcept { +static Error VirtMem_asmjitErrorFromErrno(int e) noexcept { switch (e) { case EACCES: case EAGAIN: @@ -316,6 +258,142 @@ static Error VirtMem_makeErrorFromErrno(int e) noexcept { } } +// 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 MacOS also restrict executable permissions regarding +// /dev/shm, so we use a runtime detection before attempting to allocate executable +// memory. 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 +}; + +class AnonymousMemory { +public: + enum FileType : uint32_t { + kFileTypeNone, + kFileTypeShm, + kFileTypeTmp + }; + + int _fd; + FileType _fileType; + StringTmp<128> _tmpName; + + ASMJIT_INLINE AnonymousMemory() noexcept + : _fd(-1), + _fileType(kFileTypeNone), + _tmpName() {} + + ASMJIT_INLINE ~AnonymousMemory() noexcept { + unlink(); + close(); + } + + ASMJIT_INLINE int fd() const noexcept { return _fd; } + + Error open(bool preferTmpOverDevShm) noexcept { +#if defined(__linux__) && defined(__NR_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(__NR_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_asmjitErrorFromErrno(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_asmjitErrorFromErrno(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 internalCounter; + const char* kShmFormat = "/shm-id-%016llX"; + + uint32_t kRetryCount = 100; + uint64_t bits = ((uintptr_t)(void*)this) & 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) { + _tmpName.assign(VirtMem_getTmpDir()); + _tmpName.appendFormat(kShmFormat, (unsigned long long)bits); + _fd = ::open(_tmpName.data(), O_RDWR | O_CREAT | O_EXCL, 0); + if (ASMJIT_LIKELY(_fd >= 0)) { + _fileType = kFileTypeTmp; + return kErrorOk; + } + } + else { + _tmpName.assignFormat(kShmFormat, (unsigned long long)bits); + _fd = ::shm_open(_tmpName.data(), O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + if (ASMJIT_LIKELY(_fd >= 0)) { + _fileType = kFileTypeShm; + return kErrorOk; + } + } + + int e = errno; + if (e != EEXIST) + return DebugUtils::errored(VirtMem_asmjitErrorFromErrno(e)); + } + + return DebugUtils::errored(kErrorFailedToOpenAnonymousMemory); +#endif + } + + void unlink() noexcept { + FileType type = _fileType; + _fileType = kFileTypeNone; + + if (type == kFileTypeShm) + ::shm_unlink(_tmpName.data()); + else if (type == kFileTypeTmp) + ::unlink(_tmpName.data()); + } + + void close() noexcept { + if (_fd >= 0) { + ::close(_fd); + _fd = -1; + } + } + + Error allocate(size_t size) noexcept { + // TODO: Improve this by using `posix_fallocate()` when available. + if (ftruncate(_fd, off_t(size)) != 0) + return DebugUtils::errored(VirtMem_asmjitErrorFromErrno(errno)); + + return kErrorOk; + } +}; + #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. @@ -366,103 +444,46 @@ static ASMJIT_INLINE bool VirtMem_hasMapJitSupport() noexcept { return true; #endif } +#endif -static ASMJIT_INLINE int VirtMem_appleSpecificMMapFlags(uint32_t flags) noexcept { +// Returns `mmap()` protection flags from \ref VirtMem::Flags. +static int VirtMem_mmProtFromFlags(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; +} + +// Returns either MAP_JIT or 0 based on `flags` and the host operating system. +static ASMJIT_INLINE int VirtMem_mmMapJitFromFlags(uint32_t flags) noexcept { +#if defined(__APPLE__) // 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(); + 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) noexcept { DebugUtils::unused(flags); return 0; +#endif } -#endif -#if !defined(SHM_ANON) -static const char* VirtMem_getTmpDir() noexcept { - const char* tmpDir = getenv("TMPDIR"); - return tmpDir ? tmpDir : "/tmp"; -} -#endif - -static Error VirtMem_openAnonymousMemory(AnonymousMemory* anonMem, 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) { - anonMem->fd = (int)syscall(SYS_memfd_create, "vmem", 0); - if (ASMJIT_LIKELY(anonMem->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); - anonMem->fd = shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); - - if (ASMJIT_LIKELY(anonMem->fd >= 0)) - return kErrorOk; +// Returns BSD-specific `PROT_MAX()` flags. +static ASMJIT_INLINE int VirtMem_mmMaxProtFromFlags(uint32_t flags) noexcept { +#if defined(PROT_MAX) + static constexpr uint32_t kMaxProtShift = Support::constCtz(VirtMem::kMMapMaxAccessRead); + if (flags & (VirtMem::kMMapMaxAccessReadWrite | VirtMem::kMMapMaxAccessExecute)) + return PROT_MAX(VirtMem_mmProtFromFlags(flags >> kMaxProtShift)); else - return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno)); + return 0; #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 internalCounter; - const char* kShmFormat = "/shm-id-%016llX"; - - uint32_t kRetryCount = 100; - uint64_t bits = ((uintptr_t)(void*)anonMem) & 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) { - anonMem->tmpName.assign(VirtMem_getTmpDir()); - anonMem->tmpName.appendFormat(kShmFormat, (unsigned long long)bits); - anonMem->fd = open(anonMem->tmpName.data(), O_RDWR | O_CREAT | O_EXCL, 0); - if (ASMJIT_LIKELY(anonMem->fd >= 0)) { - anonMem->fileType = AnonymousMemory::kFileTypeTmp; - return kErrorOk; - } - } - else { - anonMem->tmpName.assignFormat(kShmFormat, (unsigned long long)bits); - anonMem->fd = shm_open(anonMem->tmpName.data(), O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); - if (ASMJIT_LIKELY(anonMem->fd >= 0)) { - anonMem->fileType = AnonymousMemory::kFileTypeShm; - return kErrorOk; - } - } - - int e = errno; - if (e != EEXIST) - return DebugUtils::errored(VirtMem_makeErrorFromErrno(e)); - } - - return DebugUtils::errored(kErrorFailedToOpenAnonymousMemory); + DebugUtils::unused(flags); + return 0; #endif } @@ -471,18 +492,17 @@ static Error VirtMem_detectShmStrategy(uint32_t* strategyOut) noexcept { AnonymousMemory anonMem; VirtMem::Info vmInfo = VirtMem::info(); - ASMJIT_PROPAGATE(VirtMem_openAnonymousMemory(&anonMem, false)); - if (ftruncate(anonMem.fd, off_t(vmInfo.pageSize)) != 0) - return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno)); + ASMJIT_PROPAGATE(anonMem.open(false)); + ASMJIT_PROPAGATE(anonMem.allocate(vmInfo.pageSize)); - void* ptr = mmap(nullptr, vmInfo.pageSize, PROT_READ | PROT_EXEC, MAP_SHARED, anonMem.fd, 0); + void* ptr = mmap(nullptr, vmInfo.pageSize, PROT_READ | PROT_EXEC, MAP_SHARED, anonMem.fd(), 0); if (ptr == MAP_FAILED) { int e = errno; if (e == EINVAL) { *strategyOut = kShmStrategyTmpDir; return kErrorOk; } - return DebugUtils::errored(VirtMem_makeErrorFromErrno(e)); + return DebugUtils::errored(VirtMem_asmjitErrorFromErrno(e)); } else { munmap(ptr, vmInfo.pageSize); @@ -492,8 +512,8 @@ static Error VirtMem_detectShmStrategy(uint32_t* strategyOut) noexcept { } #endif -#if ASMJIT_VM_SHM_DETECT static Error VirtMem_getShmStrategy(uint32_t* strategyOut) noexcept { +#if ASMJIT_VM_SHM_DETECT // 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; @@ -506,24 +526,21 @@ static Error VirtMem_getShmStrategy(uint32_t* strategyOut) noexcept { *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); + int protection = VirtMem_mmProtFromFlags(flags) | VirtMem_mmMaxProtFromFlags(flags); + int mmFlags = MAP_PRIVATE | MAP_ANONYMOUS | VirtMem_mmMapJitFromFlags(flags); + void* ptr = mmap(nullptr, size, protection, mmFlags, -1, 0); if (ptr == MAP_FAILED) return DebugUtils::errored(kErrorOutOfMemory); @@ -540,7 +557,7 @@ Error VirtMem::release(void* p, size_t size) noexcept { Error VirtMem::protect(void* p, size_t size, uint32_t flags) noexcept { - int protection = VirtMem_accessToPosixProtection(flags); + int protection = VirtMem_mmProtFromFlags(flags); if (mprotect(p, size, protection) == 0) return kErrorOk; @@ -562,19 +579,21 @@ Error VirtMem::allocDualMapping(DualMapping* dm, size_t size, uint32_t flags) no } AnonymousMemory anonMem; - ASMJIT_PROPAGATE(VirtMem_openAnonymousMemory(&anonMem, preferTmpOverDevShm)); - if (ftruncate(anonMem.fd, off_t(size)) != 0) - return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno)); + ASMJIT_PROPAGATE(anonMem.open(preferTmpOverDevShm)); + ASMJIT_PROPAGATE(anonMem.allocate(size)); void* ptr[2]; for (uint32_t i = 0; i < 2; i++) { - ptr[i] = mmap(nullptr, size, VirtMem_accessToPosixProtection(flags & ~VirtMem_dualMappingFilter[i]), MAP_SHARED, anonMem.fd, 0); + uint32_t accessFlags = flags & ~VirtMem_dualMappingFilter[i]; + int protection = VirtMem_mmProtFromFlags(accessFlags) | VirtMem_mmMaxProtFromFlags(accessFlags); + + ptr[i] = mmap(nullptr, size, protection, MAP_SHARED, anonMem.fd(), 0); if (ptr[i] == MAP_FAILED) { - // Get the error now before `munmap` has a chance to clobber it. + // 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)); + return DebugUtils::errored(VirtMem_asmjitErrorFromErrno(e)); } } diff --git a/src/asmjit/core/virtmem.h b/src/asmjit/core/virtmem.h index 8d3ee01..07a2dff 100644 --- a/src/asmjit/core/virtmem.h +++ b/src/asmjit/core/virtmem.h @@ -41,27 +41,49 @@ ASMJIT_BEGIN_NAMESPACE //! Virtual memory management. namespace VirtMem { -//! Virtual memory and memory mapping flags. +//! Virtual memory access and mmap-specific flags. enum Flags : uint32_t { //! No access flags. kAccessNone = 0x00000000u, //! Memory is readable. kAccessRead = 0x00000001u, - //! Memory is writable (implies read access). + //! Memory is writable. kAccessWrite = 0x00000002u, - //! Memory is executable (implies read access). + //! Memory is executable. kAccessExecute = 0x00000004u, - //! A combination of `kAccessRead | kAccessWrite` - kAccessReadWrite = 0x00000003u, + //! A combination of \ref kAccessRead and \ref kAccessWrite. + kAccessReadWrite = kAccessRead | kAccessWrite, + //! A combination of \ref kAccessRead, \ref kAccessWrite. + kAccessRW = kAccessRead | kAccessWrite, + //! A combination of \ref kAccessRead and \ref kAccessExecute. + kAccessRX = kAccessRead | kAccessExecute, + //! A combination of \ref kAccessRead, \ref kAccessWrite, and \ref kAccessExecute. + kAccessRWX = kAccessRead | kAccessWrite | kAccessExecute, - //! Use a `MAP_JIT` flag available on Apple platforms (OSX Mojave+), which - //! allows JIT code to be executed in OSX bundles. This flag is not turned + //! Use a `MAP_JIT` flag available on Apple platforms (introduced by Mojave), + //! which allows JIT code to be executed in MAC bundles. This flag is not turned //! on by default, because when a process uses `fork()` the child process //! has no access to the pages mapped with `MAP_JIT`, which could break code //! that doesn't expect this behavior. kMMapEnableMapJit = 0x00000010u, + //! Pass `PROT_MAX(PROT_READ)` to mmap() on platforms that support `PROT_MAX`. + kMMapMaxAccessRead = 0x00000020u, + //! Pass `PROT_MAX(PROT_WRITE)` to mmap() on platforms that support `PROT_MAX`. + kMMapMaxAccessWrite = 0x00000040u, + //! Pass `PROT_MAX(PROT_EXEC)` to mmap() on platforms that support `PROT_MAX`. + kMMapMaxAccessExecute = 0x00000080u, + + //! A combination of \ref kMMapMaxAccessRead and \ref kMMapMaxAccessWrite. + kMMapMaxAccessReadWrite = kMMapMaxAccessRead | kMMapMaxAccessWrite, + //! A combination of \ref kMMapMaxAccessRead and \ref kMMapMaxAccessWrite. + kMMapMaxAccessRW = kMMapMaxAccessRead | kMMapMaxAccessWrite, + //! A combination of \ref kMMapMaxAccessRead and \ref kMMapMaxAccessExecute. + kMMapMaxAccessRX = kMMapMaxAccessRead | kMMapMaxAccessExecute, + //! A combination of \ref kMMapMaxAccessRead, \ref kMMapMaxAccessWrite, \ref kMMapMaxAccessExecute. + kMMapMaxAccessRWX = kMMapMaxAccessRead | kMMapMaxAccessWrite | kMMapMaxAccessExecute, + //! Not an access flag, only used by `allocDualMapping()` to override the //! default allocation strategy to always use a 'tmp' directory instead of //! "/dev/shm" (on POSIX platforms). Please note that this flag will be @@ -94,23 +116,22 @@ struct DualMapping { //! Returns virtual memory information, see `VirtMem::Info` for more details. ASMJIT_API Info info() noexcept; -//! Allocates virtual memory by either using `VirtualAlloc()` (Windows) -//! or `mmap()` (POSIX). +//! Allocates virtual memory by either using `mmap()` (POSIX) or `VirtualAlloc()` +//! (Windows). //! -//! \note `size` should be aligned to a page size, use \ref VirtMem::info() +//! \note `size` should be aligned to page size, use \ref VirtMem::info() //! to obtain it. Invalid size will not be corrected by the implementation //! and the allocation would not succeed in such case. ASMJIT_API Error alloc(void** p, size_t size, uint32_t flags) noexcept; -//! Releases virtual memory previously allocated by \ref VirtMem::alloc() or -//! \ref VirtMem::allocDualMapping(). +//! Releases virtual memory previously allocated by \ref VirtMem::alloc(). //! //! \note The size must be the same as used by \ref VirtMem::alloc(). If the //! size is not the same value the call will fail on any POSIX system, but -//! pass on Windows, because of the difference of the implementation. +//! pass on Windows, because it's implemented differently. ASMJIT_API Error release(void* p, size_t size) noexcept; -//! A cross-platform wrapper around `mprotect()` (POSIX) and `VirtualProtect` +//! A cross-platform wrapper around `mprotect()` (POSIX) and `VirtualProtect()` //! (Windows). ASMJIT_API Error protect(void* p, size_t size, uint32_t flags) noexcept; @@ -129,8 +150,7 @@ ASMJIT_API Error protect(void* p, size_t size, uint32_t flags) noexcept; //! \remarks Both pointers in `dm` would be set to `nullptr` if the function fails. ASMJIT_API Error allocDualMapping(DualMapping* dm, size_t size, uint32_t flags) noexcept; -//! Releases the virtual memory mapping previously allocated by -//! \ref VirtMem::allocDualMapping(). +//! Releases virtual memory mapping previously allocated by \ref VirtMem::allocDualMapping(). //! //! \remarks Both pointers in `dm` would be set to `nullptr` if the function succeeds. ASMJIT_API Error releaseDualMapping(DualMapping* dm, size_t size) noexcept;