mirror of
https://github.com/asmjit/asmjit.git
synced 2025-12-17 12:34:35 +03:00
Added support for PROT_MAX() in VirtMem::alloc() and refactored VirtMem a bit
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
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 {
|
// Returns BSD-specific `PROT_MAX()` flags.
|
||||||
#if defined(SYS_memfd_create)
|
static ASMJIT_INLINE int VirtMem_mmMaxProtFromFlags(uint32_t flags) noexcept {
|
||||||
// Linux specific 'memfd_create' - if the syscall returns `ENOSYS` it means
|
#if defined(PROT_MAX)
|
||||||
// it's not available and we will never call it again (would be pointless).
|
static constexpr uint32_t kMaxProtShift = Support::constCtz(VirtMem::kMMapMaxAccessRead);
|
||||||
|
if (flags & (VirtMem::kMMapMaxAccessReadWrite | VirtMem::kMMapMaxAccessExecute))
|
||||||
// Zero initialized, if ever changed to '1' that would mean the syscall is not
|
return PROT_MAX(VirtMem_mmProtFromFlags(flags >> kMaxProtShift));
|
||||||
// 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
|
else
|
||||||
return DebugUtils::errored(VirtMem_makeErrorFromErrno(e));
|
return 0;
|
||||||
}
|
|
||||||
#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
|
|
||||||
return DebugUtils::errored(VirtMem_makeErrorFromErrno(errno));
|
|
||||||
#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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user