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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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;
|
||||
#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<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__)
|
||||
// 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
|
||||
|
||||
#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;
|
||||
// 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(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
|
||||
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<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);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user