Added support for PROT_MAX() in VirtMem::alloc() and refactored VirtMem a bit

This commit is contained in:
kobalicek
2021-03-06 09:05:42 +01:00
parent 19f1768dbe
commit 8b35b4cffb
3 changed files with 232 additions and 193 deletions

View File

@@ -534,11 +534,11 @@ static JitAllocatorBlock* JitAllocatorImpl_newBlock(JitAllocatorPrivateImpl* imp
uint32_t blockFlags = 0; uint32_t blockFlags = 0;
if (bitWords != nullptr) { if (bitWords != nullptr) {
if (impl->options & JitAllocator::kOptionUseDualMapping) { if (impl->options & JitAllocator::kOptionUseDualMapping) {
err = VirtMem::allocDualMapping(&virtMem, blockSize, VirtMem::kAccessReadWrite | VirtMem::kAccessExecute); err = VirtMem::allocDualMapping(&virtMem, blockSize, VirtMem::kAccessRWX);
blockFlags |= JitAllocatorBlock::kFlagDualMapped; blockFlags |= JitAllocatorBlock::kFlagDualMapped;
} }
else { else {
err = VirtMem::alloc(&virtMem.ro, blockSize, VirtMem::kAccessReadWrite | VirtMem::kAccessExecute); err = VirtMem::alloc(&virtMem.ro, blockSize, VirtMem::kAccessRWX);
virtMem.rw = virtMem.ro; virtMem.rw = virtMem.ro;
} }
} }

View File

@@ -75,8 +75,8 @@ ASMJIT_BEGIN_NAMESPACE
// ============================================================================ // ============================================================================
static const uint32_t VirtMem_dualMappingFilter[2] = { static const uint32_t VirtMem_dualMappingFilter[2] = {
VirtMem::kAccessWrite, VirtMem::kAccessWrite | VirtMem::kMMapMaxAccessWrite,
VirtMem::kAccessExecute VirtMem::kAccessExecute | VirtMem::kMMapMaxAccessExecute
}; };
// ============================================================================ // ============================================================================
@@ -104,14 +104,14 @@ static void VirtMem_getInfo(VirtMem::Info& vmInfo) noexcept {
vmInfo.pageGranularity = systemInfo.dwAllocationGranularity; vmInfo.pageGranularity = systemInfo.dwAllocationGranularity;
} }
// Windows specific implementation that uses `VirtualAlloc` and `VirtualFree`. // Returns windows-specific protectFlags from \ref VirtMem::Flags.
static DWORD VirtMem_accessToWinProtectFlags(uint32_t flags) noexcept { static DWORD VirtMem_winProtectFlagsFromFlags(uint32_t flags) noexcept {
DWORD protectFlags; DWORD protectFlags;
// READ|WRITE|EXECUTE. // READ|WRITE|EXECUTE.
if (flags & VirtMem::kAccessExecute) if (flags & VirtMem::kAccessExecute)
protectFlags = (flags & VirtMem::kAccessWrite) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 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; protectFlags = (flags & VirtMem::kAccessWrite) ? PAGE_READWRITE : PAGE_READONLY;
else else
protectFlags = PAGE_NOACCESS; protectFlags = PAGE_NOACCESS;
@@ -120,7 +120,7 @@ static DWORD VirtMem_accessToWinProtectFlags(uint32_t flags) noexcept {
return protectFlags; 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; DWORD access = (flags & VirtMem::kAccessWrite) ? FILE_MAP_WRITE : FILE_MAP_READ;
if (flags & VirtMem::kAccessExecute) if (flags & VirtMem::kAccessExecute)
access |= FILE_MAP_EXECUTE; access |= FILE_MAP_EXECUTE;
@@ -132,7 +132,7 @@ Error VirtMem::alloc(void** p, size_t size, uint32_t flags) noexcept {
if (size == 0) if (size == 0)
return DebugUtils::errored(kErrorInvalidArgument); return DebugUtils::errored(kErrorInvalidArgument);
DWORD protectFlags = VirtMem_accessToWinProtectFlags(flags); DWORD protectFlags = VirtMem_winProtectFlagsFromFlags(flags);
void* result = ::VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, protectFlags); void* result = ::VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, protectFlags);
if (!result) 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 { Error VirtMem::protect(void* p, size_t size, uint32_t flags) noexcept {
DWORD protectFlags = VirtMem_accessToWinProtectFlags(flags); DWORD protectFlags = VirtMem_winProtectFlagsFromFlags(flags);
DWORD oldFlags; DWORD oldFlags;
if (::VirtualProtect(p, size, protectFlags, &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]; void* ptr[2];
for (uint32_t i = 0; i < 2; i++) { 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); ptr[i] = ::MapViewOfFile(handle.value, desiredAccess, 0, 0, size);
if (ptr[i] == nullptr) { if (ptr[i] == nullptr) {
@@ -219,51 +220,6 @@ Error VirtMem::releaseDualMapping(DualMapping* dm, size_t size) noexcept {
// ============================================================================ // ============================================================================
#if !defined(_WIN32) #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 { static void VirtMem_getInfo(VirtMem::Info& vmInfo) noexcept {
uint32_t pageSize = uint32_t(::getpagesize()); uint32_t pageSize = uint32_t(::getpagesize());
@@ -272,29 +228,15 @@ static void VirtMem_getInfo(VirtMem::Info& vmInfo) noexcept {
vmInfo.pageGranularity = Support::max<uint32_t>(pageSize, 65536); vmInfo.pageGranularity = Support::max<uint32_t>(pageSize, 65536);
} }
// Some operating systems don't allow /dev/shm to be executable. On Linux this #if !defined(SHM_ANON)
// happens when /dev/shm is mounted with 'noexec', which is enforced by systemd. static const char* VirtMem_getTmpDir() noexcept {
// Other operating systems like OSX also restrict executable permissions regarding const char* tmpDir = getenv("TMPDIR");
// /dev/shm, so we use a runtime detection before trying to allocate the requested return tmpDir ? tmpDir : "/tmp";
// 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;
} }
#endif
// Translates libc errors specific to VirtualMemory mapping to `asmjit::Error`. // 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) { switch (e) {
case EACCES: case EACCES:
case EAGAIN: 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<uint32_t> 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__) #if defined(__APPLE__)
// Detects whether the current process is hardened, which means that pages that // Detects whether the current process is hardened, which means that pages that
// have WRITE and EXECUTABLE flags cannot be allocated without MAP_JIT flag. // 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; return true;
#endif #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 // 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 // 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 // process is actually hardened (in that case it doesn't make sense to rely
// on user `flags`). // on user `flags`).
bool useMapJit = ((flags & VirtMem::kMMapEnableMapJit) != 0) || VirtMem_isHardened(); bool useMapJit = (flags & VirtMem::kMMapEnableMapJit) != 0 || VirtMem_isHardened();
if (useMapJit) if (useMapJit)
return VirtMem_hasMapJitSupport() ? int(MAP_JIT) : 0; return VirtMem_hasMapJitSupport() ? int(MAP_JIT) : 0;
else else
return 0; return 0;
}
#else #else
static ASMJIT_INLINE int VirtMem_appleSpecificMMapFlags(uint32_t flags) noexcept {
DebugUtils::unused(flags); DebugUtils::unused(flags);
return 0; return 0;
#endif
} }
#endif
#if !defined(SHM_ANON) // Returns BSD-specific `PROT_MAX()` flags.
static const char* VirtMem_getTmpDir() noexcept { static ASMJIT_INLINE int VirtMem_mmMaxProtFromFlags(uint32_t flags) noexcept {
const char* tmpDir = getenv("TMPDIR"); #if defined(PROT_MAX)
return tmpDir ? tmpDir : "/tmp"; static constexpr uint32_t kMaxProtShift = Support::constCtz(VirtMem::kMMapMaxAccessRead);
} if (flags & (VirtMem::kMMapMaxAccessReadWrite | VirtMem::kMMapMaxAccessExecute))
#endif return PROT_MAX(VirtMem_mmProtFromFlags(flags >> kMaxProtShift));
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;
else else
return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno)); return 0;
#else #else
// POSIX API. We have to generate somehow a unique name. This is nothing DebugUtils::unused(flags);
// cryptographic, just using a bit from the stack address to always have return 0;
// 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;
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);
#endif #endif
} }
@@ -471,18 +492,17 @@ static Error VirtMem_detectShmStrategy(uint32_t* strategyOut) noexcept {
AnonymousMemory anonMem; AnonymousMemory anonMem;
VirtMem::Info vmInfo = VirtMem::info(); VirtMem::Info vmInfo = VirtMem::info();
ASMJIT_PROPAGATE(VirtMem_openAnonymousMemory(&anonMem, false)); ASMJIT_PROPAGATE(anonMem.open(false));
if (ftruncate(anonMem.fd, off_t(vmInfo.pageSize)) != 0) ASMJIT_PROPAGATE(anonMem.allocate(vmInfo.pageSize));
return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno));
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) { if (ptr == MAP_FAILED) {
int e = errno; int e = errno;
if (e == EINVAL) { if (e == EINVAL) {
*strategyOut = kShmStrategyTmpDir; *strategyOut = kShmStrategyTmpDir;
return kErrorOk; return kErrorOk;
} }
return DebugUtils::errored(VirtMem_makeErrorFromErrno(e)); return DebugUtils::errored(VirtMem_asmjitErrorFromErrno(e));
} }
else { else {
munmap(ptr, vmInfo.pageSize); munmap(ptr, vmInfo.pageSize);
@@ -492,8 +512,8 @@ static Error VirtMem_detectShmStrategy(uint32_t* strategyOut) noexcept {
} }
#endif #endif
#if ASMJIT_VM_SHM_DETECT
static Error VirtMem_getShmStrategy(uint32_t* strategyOut) noexcept { static Error VirtMem_getShmStrategy(uint32_t* strategyOut) noexcept {
#if ASMJIT_VM_SHM_DETECT
// Initially don't assume anything. It has to be tested whether // Initially don't assume anything. It has to be tested whether
// '/dev/shm' was mounted with 'noexec' flag or not. // '/dev/shm' was mounted with 'noexec' flag or not.
static volatile uint32_t globalShmStrategy = kShmStrategyUnknown; static volatile uint32_t globalShmStrategy = kShmStrategyUnknown;
@@ -506,24 +526,21 @@ static Error VirtMem_getShmStrategy(uint32_t* strategyOut) noexcept {
*strategyOut = strategy; *strategyOut = strategy;
return kErrorOk; return kErrorOk;
}
#else #else
static Error VirtMem_getShmStrategy(uint32_t* strategyOut) noexcept {
*strategyOut = kShmStrategyTmpDir; *strategyOut = kShmStrategyTmpDir;
return kErrorOk; return kErrorOk;
}
#endif #endif
}
Error VirtMem::alloc(void** p, size_t size, uint32_t flags) noexcept { Error VirtMem::alloc(void** p, size_t size, uint32_t flags) noexcept {
*p = nullptr; *p = nullptr;
if (size == 0) if (size == 0)
return DebugUtils::errored(kErrorInvalidArgument); return DebugUtils::errored(kErrorInvalidArgument);
int protection = VirtMem_accessToPosixProtection(flags); int protection = VirtMem_mmProtFromFlags(flags) | VirtMem_mmMaxProtFromFlags(flags);
int mmFlags = MAP_PRIVATE | MAP_ANONYMOUS | VirtMem_appleSpecificMMapFlags(flags); int mmFlags = MAP_PRIVATE | MAP_ANONYMOUS | VirtMem_mmMapJitFromFlags(flags);
void* ptr = mmap(nullptr, size, protection, mmFlags, -1, 0);
void* ptr = mmap(nullptr, size, protection, mmFlags, -1, 0);
if (ptr == MAP_FAILED) if (ptr == MAP_FAILED)
return DebugUtils::errored(kErrorOutOfMemory); 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 { 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) if (mprotect(p, size, protection) == 0)
return kErrorOk; return kErrorOk;
@@ -562,19 +579,21 @@ Error VirtMem::allocDualMapping(DualMapping* dm, size_t size, uint32_t flags) no
} }
AnonymousMemory anonMem; AnonymousMemory anonMem;
ASMJIT_PROPAGATE(VirtMem_openAnonymousMemory(&anonMem, preferTmpOverDevShm)); ASMJIT_PROPAGATE(anonMem.open(preferTmpOverDevShm));
if (ftruncate(anonMem.fd, off_t(size)) != 0) ASMJIT_PROPAGATE(anonMem.allocate(size));
return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno));
void* ptr[2]; void* ptr[2];
for (uint32_t i = 0; i < 2; i++) { 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) { 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; int e = errno;
if (i == 1) if (i == 1)
munmap(ptr[0], size); munmap(ptr[0], size);
return DebugUtils::errored(VirtMem_makeErrorFromErrno(e)); return DebugUtils::errored(VirtMem_asmjitErrorFromErrno(e));
} }
} }

View File

@@ -41,27 +41,49 @@ ASMJIT_BEGIN_NAMESPACE
//! Virtual memory management. //! Virtual memory management.
namespace VirtMem { namespace VirtMem {
//! Virtual memory and memory mapping flags. //! Virtual memory access and mmap-specific flags.
enum Flags : uint32_t { enum Flags : uint32_t {
//! No access flags. //! No access flags.
kAccessNone = 0x00000000u, kAccessNone = 0x00000000u,
//! Memory is readable. //! Memory is readable.
kAccessRead = 0x00000001u, kAccessRead = 0x00000001u,
//! Memory is writable (implies read access). //! Memory is writable.
kAccessWrite = 0x00000002u, kAccessWrite = 0x00000002u,
//! Memory is executable (implies read access). //! Memory is executable.
kAccessExecute = 0x00000004u, kAccessExecute = 0x00000004u,
//! A combination of `kAccessRead | kAccessWrite` //! A combination of \ref kAccessRead and \ref kAccessWrite.
kAccessReadWrite = 0x00000003u, 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 //! Use a `MAP_JIT` flag available on Apple platforms (introduced by Mojave),
//! allows JIT code to be executed in OSX bundles. This flag is not turned //! 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 //! 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 //! has no access to the pages mapped with `MAP_JIT`, which could break code
//! that doesn't expect this behavior. //! that doesn't expect this behavior.
kMMapEnableMapJit = 0x00000010u, 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 //! Not an access flag, only used by `allocDualMapping()` to override the
//! default allocation strategy to always use a 'tmp' directory instead of //! default allocation strategy to always use a 'tmp' directory instead of
//! "/dev/shm" (on POSIX platforms). Please note that this flag will be //! "/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. //! Returns virtual memory information, see `VirtMem::Info` for more details.
ASMJIT_API Info info() noexcept; ASMJIT_API Info info() noexcept;
//! Allocates virtual memory by either using `VirtualAlloc()` (Windows) //! Allocates virtual memory by either using `mmap()` (POSIX) or `VirtualAlloc()`
//! or `mmap()` (POSIX). //! (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 //! to obtain it. Invalid size will not be corrected by the implementation
//! and the allocation would not succeed in such case. //! and the allocation would not succeed in such case.
ASMJIT_API Error alloc(void** p, size_t size, uint32_t flags) noexcept; ASMJIT_API Error alloc(void** p, size_t size, uint32_t flags) noexcept;
//! Releases virtual memory previously allocated by \ref VirtMem::alloc() or //! Releases virtual memory previously allocated by \ref VirtMem::alloc().
//! \ref VirtMem::allocDualMapping().
//! //!
//! \note The size must be the same as used by \ref VirtMem::alloc(). If the //! \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 //! 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; 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). //! (Windows).
ASMJIT_API Error protect(void* p, size_t size, uint32_t flags) noexcept; 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. //! \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; ASMJIT_API Error allocDualMapping(DualMapping* dm, size_t size, uint32_t flags) noexcept;
//! Releases the virtual memory mapping previously allocated by //! Releases virtual memory mapping previously allocated by \ref VirtMem::allocDualMapping().
//! \ref VirtMem::allocDualMapping().
//! //!
//! \remarks Both pointers in `dm` would be set to `nullptr` if the function succeeds. //! \remarks Both pointers in `dm` would be set to `nullptr` if the function succeeds.
ASMJIT_API Error releaseDualMapping(DualMapping* dm, size_t size) noexcept; ASMJIT_API Error releaseDualMapping(DualMapping* dm, size_t size) noexcept;