mirror of
https://github.com/asmjit/asmjit.git
synced 2025-12-17 12:34:35 +03:00
Use MFD_CLOEXEC when using memfd_create, documentation update
This commit is contained in:
@@ -1633,14 +1633,57 @@ namespace asmjit {
|
|||||||
//!
|
//!
|
||||||
//! ### Overview
|
//! ### Overview
|
||||||
//!
|
//!
|
||||||
//! AsmJit's virtual memory management is divided into two main categories:
|
//! AsmJit's virtual memory management is divided into three main categories:
|
||||||
//!
|
//!
|
||||||
//! - Low level API that provides cross-platform abstractions for virtual memory allocation. Implemented in
|
//! - Low level interface that provides cross-platform abstractions for virtual memory allocation. Implemented in
|
||||||
//! \ref VirtMem namespace.
|
//! \ref VirtMem namespace. This API is a thin wrapper around operating system specific calls such as
|
||||||
|
//! `VirtualAlloc()` and `mmap()` and it's intended to be used by AsmJit's higher level API. Low-level virtual
|
||||||
|
//! memory functions can be used to allocate virtual memory, change its permissions, and to release it.
|
||||||
|
//! Additionally, an API that allows to create dual mapping (to support hardened environments) is provided.
|
||||||
//!
|
//!
|
||||||
//! - High level API that makes it very easy to store generated code for execution. See \ref JitRuntime, which is
|
//! - Middle level API that is provided by \ref JitAllocator, which uses \ref VirtMem internally and offers nicer
|
||||||
//! used by many examples for its simplicity and easy integration with \ref CodeHolder. There is also \ref
|
//! API that can be used by users to allocate executable memory conveniently. \ref JitAllocator tries to be smart,
|
||||||
//! JitAllocator, which lays somewhere between RAW memory allocation and \ref JitRuntime.
|
//! for example automatically using dual mapping or `MAP_JIT` on hardened environments.
|
||||||
|
//!
|
||||||
|
//! - High level API that is provided by \ref JitRuntime, which implements \ref Target interface and uses \ref
|
||||||
|
//! JitAllocator under the hood. Since \ref JitRuntime inherits from \ref Target it makes it easy to use with
|
||||||
|
//! \ref CodeHolder. Many AsmJit examples use \ref JitRuntime for its simplicity and easy integration.
|
||||||
|
//!
|
||||||
|
//! The main difference between \ref VirtMem and \ref JitAllocator is that \ref VirtMem can only be used to allocate
|
||||||
|
//! whole pages, whereas \ref JitAllocator has `malloc()` like API that allows to allocate smaller quantities that
|
||||||
|
//! usually represent the size of an assembled function or a chunk of functions that can represent a module, for
|
||||||
|
//! example. \ref JitAllocator then tracks used space of each page it maintains. Internally, \ref JitAllocator uses
|
||||||
|
//! two bit arrays to track occupied regions in each allocated block of pages.
|
||||||
|
//!
|
||||||
|
//! ### Hardened Environments
|
||||||
|
//!
|
||||||
|
//! In the past, allocating virtual memory with Read+Write+Execute (RWX) access permissions was easy. However, modern
|
||||||
|
//! operating systems and runtime environments often use hardening, which typically prohibits mapping pages with both
|
||||||
|
//! Write and Execute permissions (known as the W^X policy). This presents a challenge for JIT compilers because
|
||||||
|
//! generated code for a single function is unlikely to fit in exactly N pages without leaving some space empty. To
|
||||||
|
//! accommodate this, the execution environment may need to temporarily change the permissions of existing pages to
|
||||||
|
//! read+write (RW) to insert new code into them, however, sometimes it's not possible to ensure that no thread is
|
||||||
|
//! executing code in such affected pages in a multithreaded environment, in which multiple threads may be executing
|
||||||
|
//! generated code.
|
||||||
|
//!
|
||||||
|
//! Such restrictions leave a lot of complexity on the application, so AsmJit implements a dual mapping technique to
|
||||||
|
//! make the life of AsmJit users easier. In this technique, a region of memory is mapped to two different virtual
|
||||||
|
//! addresses with different access permissions. One virtual address is mapped with read and write (RW) access, which
|
||||||
|
//! is used by the JIT compiler to write generated code. The other virtual address is mapped with read and execute (RX)
|
||||||
|
//! access, which is used by the application to execute the generated code.
|
||||||
|
//!
|
||||||
|
//! However, implementing dual mapping can be challenging because it typically requires obtaining an anonymous file
|
||||||
|
//! descriptor on most Unix-like operating systems. This file descriptor is then passed to mmap() twice to create
|
||||||
|
//! the two mappings. AsmJit handles this challenge by using system-specific techniques such as `memfd_create()` on
|
||||||
|
//! Linux, `shm_open(SHM_ANON)` on BSD, and `MAP_REMAPDUP` with `mremap()` on NetBSD. The latter approach does not
|
||||||
|
//! require a file descriptor. If none of these options are available, AsmJit uses a plain `open()` call followed by
|
||||||
|
//! `unlink()`.
|
||||||
|
//!
|
||||||
|
//! The most challenging part is actually obtaing a file descriptor that can be passed to `mmap()` with `PROT_EXEC`.
|
||||||
|
//! This is still something that may fail, for example the environment could be hardened in a way that this would
|
||||||
|
//! not be possible at all, and thus dual mapping would not work.
|
||||||
|
//!
|
||||||
|
//! Dual mapping is provided by both \ref VirtMem and \ref JitAllocator.
|
||||||
|
|
||||||
|
|
||||||
//! \defgroup asmjit_zone Zone Memory
|
//! \defgroup asmjit_zone Zone Memory
|
||||||
|
|||||||
@@ -544,7 +544,7 @@ static Error JitAllocatorImpl_newBlock(JitAllocatorPrivateImpl* impl, JitAllocat
|
|||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
else
|
else
|
||||||
return kErrorOutOfMemory;
|
return DebugUtils::errored(kErrorOutOfMemory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill the memory if the secure mode is enabled.
|
// Fill the memory if the secure mode is enabled.
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
// Android NDK doesn't provide `shm_open()` and `shm_unlink()`.
|
// Android NDK doesn't provide `shm_open()` and `shm_unlink()`.
|
||||||
#if !defined(__BIONIC__)
|
#if !defined(__BIONIC__)
|
||||||
#define ASMJIT_HAS_SHM_OPEN_AND_UNLINK
|
#define ASMJIT_HAS_SHM_OPEN
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__APPLE__) && TARGET_OS_OSX && ASMJIT_ARCH_ARM >= 64
|
#if defined(__APPLE__) && TARGET_OS_OSX && ASMJIT_ARCH_ARM >= 64
|
||||||
@@ -332,6 +332,11 @@ public:
|
|||||||
|
|
||||||
Error open(bool preferTmpOverDevShm) noexcept {
|
Error open(bool preferTmpOverDevShm) noexcept {
|
||||||
#if defined(__linux__) && defined(__NR_memfd_create)
|
#if defined(__linux__) && defined(__NR_memfd_create)
|
||||||
|
|
||||||
|
#if !defined(MFD_CLOEXEC)
|
||||||
|
#define MFD_CLOEXEC 0x0001u
|
||||||
|
#endif
|
||||||
|
|
||||||
// Linux specific 'memfd_create' - if the syscall returns `ENOSYS` it means
|
// 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).
|
// it's not available and we will never call it again (would be pointless).
|
||||||
//
|
//
|
||||||
@@ -344,7 +349,7 @@ public:
|
|||||||
static volatile uint32_t memfd_create_not_supported;
|
static volatile uint32_t memfd_create_not_supported;
|
||||||
|
|
||||||
if (!memfd_create_not_supported) {
|
if (!memfd_create_not_supported) {
|
||||||
_fd = (int)syscall(__NR_memfd_create, "vmem", 0);
|
_fd = (int)syscall(__NR_memfd_create, "vmem", MFD_CLOEXEC);
|
||||||
if (ASMJIT_LIKELY(_fd >= 0))
|
if (ASMJIT_LIKELY(_fd >= 0))
|
||||||
return kErrorOk;
|
return kErrorOk;
|
||||||
|
|
||||||
@@ -356,7 +361,7 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(SHM_ANON)
|
#if defined(ASMJIT_HAS_SHM_OPEN) && defined(SHM_ANON)
|
||||||
// Originally FreeBSD extension, apparently works in other BSDs too.
|
// Originally FreeBSD extension, apparently works in other BSDs too.
|
||||||
DebugUtils::unused(preferTmpOverDevShm);
|
DebugUtils::unused(preferTmpOverDevShm);
|
||||||
_fd = ::shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
|
_fd = ::shm_open(SHM_ANON, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
|
||||||
@@ -391,7 +396,7 @@ public:
|
|||||||
return kErrorOk;
|
return kErrorOk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef ASMJIT_HAS_SHM_OPEN_AND_UNLINK
|
#if defined(ASMJIT_HAS_SHM_OPEN)
|
||||||
else {
|
else {
|
||||||
_tmpName.assignFormat(kShmFormat, (unsigned long long)bits);
|
_tmpName.assignFormat(kShmFormat, (unsigned long long)bits);
|
||||||
_fd = ::shm_open(_tmpName.data(), O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
|
_fd = ::shm_open(_tmpName.data(), O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
|
||||||
@@ -415,7 +420,7 @@ public:
|
|||||||
FileType type = _fileType;
|
FileType type = _fileType;
|
||||||
_fileType = kFileTypeNone;
|
_fileType = kFileTypeNone;
|
||||||
|
|
||||||
#ifdef ASMJIT_HAS_SHM_OPEN_AND_UNLINK
|
#ifdef ASMJIT_HAS_SHM_OPEN
|
||||||
if (type == kFileTypeShm) {
|
if (type == kFileTypeShm) {
|
||||||
::shm_unlink(_tmpName.data());
|
::shm_unlink(_tmpName.data());
|
||||||
return;
|
return;
|
||||||
@@ -511,9 +516,9 @@ static bool hasHardenedRuntime() noexcept {
|
|||||||
|
|
||||||
uint32_t flag = globalHardenedFlag.load();
|
uint32_t flag = globalHardenedFlag.load();
|
||||||
if (flag == kHardenedFlagUnknown) {
|
if (flag == kHardenedFlagUnknown) {
|
||||||
uint32_t pageSize = uint32_t(::getpagesize());
|
size_t pageSize = size_t(::getpagesize());
|
||||||
|
|
||||||
void* ptr = mmap(nullptr, pageSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
void* ptr = mmap(nullptr, pageSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
|
||||||
if (ptr == MAP_FAILED) {
|
if (ptr == MAP_FAILED) {
|
||||||
flag = kHardenedFlagEnabled;
|
flag = kHardenedFlagEnabled;
|
||||||
}
|
}
|
||||||
@@ -521,6 +526,7 @@ static bool hasHardenedRuntime() noexcept {
|
|||||||
flag = kHardenedFlagDisabled;
|
flag = kHardenedFlagDisabled;
|
||||||
munmap(ptr, pageSize);
|
munmap(ptr, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
globalHardenedFlag.store(flag);
|
globalHardenedFlag.store(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,57 +50,71 @@ enum class MemoryFlags : uint32_t {
|
|||||||
//! Memory is executable.
|
//! Memory is executable.
|
||||||
kAccessExecute = 0x00000004u,
|
kAccessExecute = 0x00000004u,
|
||||||
|
|
||||||
//! A combination of \ref MemoryFlags::kAccessRead and \ref MemoryFlags::kAccessWrite.
|
//! A combination of \ref kAccessRead and \ref kAccessWrite.
|
||||||
kAccessReadWrite = kAccessRead | kAccessWrite,
|
kAccessReadWrite = kAccessRead | kAccessWrite,
|
||||||
|
|
||||||
//! A combination of \ref MemoryFlags::kAccessRead, \ref MemoryFlags::kAccessWrite.
|
//! A combination of \ref kAccessRead, \ref kAccessWrite.
|
||||||
kAccessRW = kAccessRead | kAccessWrite,
|
kAccessRW = kAccessRead | kAccessWrite,
|
||||||
|
|
||||||
//! A combination of \ref MemoryFlags::kAccessRead and \ref MemoryFlags::kAccessExecute.
|
//! A combination of \ref kAccessRead and \ref kAccessExecute.
|
||||||
kAccessRX = kAccessRead | kAccessExecute,
|
kAccessRX = kAccessRead | kAccessExecute,
|
||||||
|
|
||||||
//! A combination of \ref MemoryFlags::kAccessRead, \ref MemoryFlags::kAccessWrite, and
|
//! A combination of \ref kAccessRead, \ref kAccessWrite, and \ref kAccessExecute.
|
||||||
//! \ref MemoryFlags::kAccessExecute.
|
|
||||||
kAccessRWX = kAccessRead | kAccessWrite | kAccessExecute,
|
kAccessRWX = kAccessRead | kAccessWrite | kAccessExecute,
|
||||||
|
|
||||||
//! Use a `MAP_JIT` flag available on Apple platforms (introduced by Mojave), which allows JIT code to be executed
|
//! Use a `MAP_JIT` flag available on Apple platforms (introduced by Mojave), which allows JIT code to be
|
||||||
//! in MAC bundles. This flag is not turned on by default, because when a process uses `fork()` the child process
|
//! executed in a MAC bundle.
|
||||||
//! has no access to the pages mapped with `MAP_JIT`, which could break code that doesn't expect this behavior.
|
//!
|
||||||
|
//! This flag may be turned on by the allocator if there is no other way of allocating executable memory.
|
||||||
//!
|
//!
|
||||||
//! \note This flag can only be used with \ref VirtMem::alloc(), `MAP_JIT` only works on OSX and not on iOS.
|
//! \note This flag can only be used with \ref VirtMem::alloc(), `MAP_JIT` only works on OSX and not on iOS.
|
||||||
|
//! When a process uses `fork()` the child process has no access to the pages mapped with `MAP_JIT`.
|
||||||
kMMapEnableMapJit = 0x00000010u,
|
kMMapEnableMapJit = 0x00000010u,
|
||||||
|
|
||||||
//! Pass `PROT_MAX(PROT_READ)` to mmap() on platforms that support `PROT_MAX`.
|
//! Pass `PROT_MAX(PROT_READ)` or `PROT_MPROTECT(PROT_READ)` to `mmap()` on platforms that support it.
|
||||||
//!
|
//!
|
||||||
//! \note This flag can only be used with \ref VirtMem::alloc().
|
//! This flag allows to set a "maximum access" that the memory page can get during its lifetime. Use
|
||||||
|
//! \ref VirtMem::protect() to change the access flags.
|
||||||
|
//!
|
||||||
|
//! \note This flag can only be used with \ref VirtMem::alloc() and \ref VirtMem::allocDualMapping().
|
||||||
|
//! However \ref VirtMem::allocDualMapping() may automatically use this if \ref kAccessRead is used.
|
||||||
kMMapMaxAccessRead = 0x00000020u,
|
kMMapMaxAccessRead = 0x00000020u,
|
||||||
//! Pass `PROT_MAX(PROT_WRITE)` to mmap() on platforms that support `PROT_MAX`.
|
|
||||||
|
//! Pass `PROT_MAX(PROT_WRITE)` or `PROT_MPROTECT(PROT_WRITE)` to `mmap()` on platforms that support it.
|
||||||
//!
|
//!
|
||||||
//! \note This flag can only be used with \ref VirtMem::alloc().
|
//! This flag allows to set a "maximum access" that the memory page can get during its lifetime. Use
|
||||||
|
//! \ref VirtMem::protect() to change the access flags.
|
||||||
|
//!
|
||||||
|
//! \note This flag can only be used with \ref VirtMem::alloc() and \ref VirtMem::allocDualMapping().
|
||||||
|
//! However \ref VirtMem::allocDualMapping() may automatically use this if \ref kAccessWrite is used.
|
||||||
kMMapMaxAccessWrite = 0x00000040u,
|
kMMapMaxAccessWrite = 0x00000040u,
|
||||||
//! Pass `PROT_MAX(PROT_EXEC)` to mmap() on platforms that support `PROT_MAX`.
|
|
||||||
|
//! Pass `PROT_MAX(PROT_EXEC)` or `PROT_MPROTECT(PROT_EXEC)` to `mmap()` on platforms that support it.
|
||||||
//!
|
//!
|
||||||
//! \note This flag can only be used with \ref VirtMem::alloc().
|
//! This flag allows to set a "maximum access" that the memory page can get during its lifetime. Use
|
||||||
|
//! \ref VirtMem::protect() to change the access flags.
|
||||||
|
//!
|
||||||
|
//! \note This flag can only be used with \ref VirtMem::alloc() and \ref VirtMem::allocDualMapping().
|
||||||
|
//! However \ref VirtMem::allocDualMapping() may automatically use this if \ref kAccessExecute is used.
|
||||||
kMMapMaxAccessExecute = 0x00000080u,
|
kMMapMaxAccessExecute = 0x00000080u,
|
||||||
|
|
||||||
//! A combination of \ref MemoryFlags::kMMapMaxAccessRead and \ref MemoryFlags::kMMapMaxAccessWrite.
|
//! A combination of \ref kMMapMaxAccessRead and \ref kMMapMaxAccessWrite.
|
||||||
kMMapMaxAccessReadWrite = kMMapMaxAccessRead | kMMapMaxAccessWrite,
|
kMMapMaxAccessReadWrite = kMMapMaxAccessRead | kMMapMaxAccessWrite,
|
||||||
|
|
||||||
//! A combination of \ref MemoryFlags::kMMapMaxAccessRead and \ref MemoryFlags::kMMapMaxAccessWrite.
|
//! A combination of \ref kMMapMaxAccessRead and \ref kMMapMaxAccessWrite.
|
||||||
kMMapMaxAccessRW = kMMapMaxAccessRead | kMMapMaxAccessWrite,
|
kMMapMaxAccessRW = kMMapMaxAccessRead | kMMapMaxAccessWrite,
|
||||||
|
|
||||||
//! A combination of \ref MemoryFlags::kMMapMaxAccessRead and \ref MemoryFlags::kMMapMaxAccessExecute.
|
//! A combination of \ref kMMapMaxAccessRead and \ref kMMapMaxAccessExecute.
|
||||||
kMMapMaxAccessRX = kMMapMaxAccessRead | kMMapMaxAccessExecute,
|
kMMapMaxAccessRX = kMMapMaxAccessRead | kMMapMaxAccessExecute,
|
||||||
|
|
||||||
//! A combination of \ref MemoryFlags::kMMapMaxAccessRead, \ref MemoryFlags::kMMapMaxAccessWrite, \ref
|
//! A combination of \ref kMMapMaxAccessRead, \ref kMMapMaxAccessWrite, \ref kMMapMaxAccessExecute.
|
||||||
//! MemoryFlags::kMMapMaxAccessExecute.
|
|
||||||
kMMapMaxAccessRWX = kMMapMaxAccessRead | kMMapMaxAccessWrite | kMMapMaxAccessExecute,
|
kMMapMaxAccessRWX = kMMapMaxAccessRead | kMMapMaxAccessWrite | kMMapMaxAccessExecute,
|
||||||
|
|
||||||
//! Use `MAP_SHARED` when calling mmap().
|
//! Use `MAP_SHARED` when calling mmap().
|
||||||
//!
|
//!
|
||||||
//! \note In some cases `MAP_SHARED` may be set automatically. For example when using dual mapping it's important to
|
//! \note In some cases `MAP_SHARED` may be set automatically. For example, some dual mapping implementations must
|
||||||
//! to use `MAP_SHARED` instead of `MAP_PRIVATE` to ensure that the OS would not copy pages on write (that would mean
|
//! use `MAP_SHARED` instead of `MAP_PRIVATE` to ensure that the OS would not apply copy on write on RW page, which
|
||||||
//! updating only the RW mapped region and not RX mapped one).
|
//! would cause RX page not having the updated content.
|
||||||
kMapShared = 0x00000100u,
|
kMapShared = 0x00000100u,
|
||||||
|
|
||||||
//! Not an access flag, only used by `allocDualMapping()` to override the default allocation strategy to always use
|
//! Not an access flag, only used by `allocDualMapping()` to override the default allocation strategy to always use
|
||||||
|
|||||||
Reference in New Issue
Block a user