summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/asmjit/src/asmjit/core/virtmem.h
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/asmjit/src/asmjit/core/virtmem.h')
-rw-r--r--3rdparty/asmjit/src/asmjit/core/virtmem.h362
1 files changed, 272 insertions, 90 deletions
diff --git a/3rdparty/asmjit/src/asmjit/core/virtmem.h b/3rdparty/asmjit/src/asmjit/core/virtmem.h
index 8d3ee01ee5b..17996dcb07e 100644
--- a/3rdparty/asmjit/src/asmjit/core/virtmem.h
+++ b/3rdparty/asmjit/src/asmjit/core/virtmem.h
@@ -1,25 +1,7 @@
-// AsmJit - Machine code generation for C++
+// This file is part of AsmJit project <https://asmjit.com>
//
-// * Official AsmJit Home Page: https://asmjit.com
-// * Official Github Repository: https://github.com/asmjit/asmjit
-//
-// Copyright (c) 2008-2020 The AsmJit Authors
-//
-// This software is provided 'as-is', without any express or implied
-// warranty. In no event will the authors be held liable for any damages
-// arising from the use of this software.
-//
-// Permission is granted to anyone to use this software for any purpose,
-// including commercial applications, and to alter it and redistribute it
-// freely, subject to the following restrictions:
-//
-// 1. The origin of this software must not be misrepresented; you must not
-// claim that you wrote the original software. If you use this software
-// in a product, an acknowledgment in the product documentation would be
-// appreciated but is not required.
-// 2. Altered source versions must be plainly marked as such, and must not be
-// misrepresented as being the original software.
-// 3. This notice may not be removed or altered from any source distribution.
+// See asmjit.h or LICENSE.md for license and copyright information
+// SPDX-License-Identifier: Zlib
#ifndef ASMJIT_CORE_VIRTMEM_H_INCLUDED
#define ASMJIT_CORE_VIRTMEM_H_INCLUDED
@@ -28,50 +10,37 @@
#ifndef ASMJIT_NO_JIT
#include "../core/globals.h"
+#include "../core/support.h"
ASMJIT_BEGIN_NAMESPACE
//! \addtogroup asmjit_virtual_memory
//! \{
-// ============================================================================
-// [asmjit::VirtMem]
-// ============================================================================
-
//! Virtual memory management.
namespace VirtMem {
-//! Virtual memory and memory mapping flags.
-enum Flags : uint32_t {
- //! No access flags.
- kAccessNone = 0x00000000u,
- //! Memory is readable.
- kAccessRead = 0x00000001u,
- //! Memory is writable (implies read access).
- kAccessWrite = 0x00000002u,
- //! Memory is executable (implies read access).
- kAccessExecute = 0x00000004u,
+//! Describes whether instruction cache should be flushed after a write operation.
+enum class CachePolicy : uint32_t {
+ //! Default policy.
+ //!
+ //! In some places this would mean `kFlushAfterWrite` and in some places it would mean `kNeverFlush`.
+ //! For example if it's known that an address has never been used before to execute code.
+ kDefault = 0,
- //! A combination of `kAccessRead | kAccessWrite`
- kAccessReadWrite = 0x00000003u,
-
- //! 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
- //! 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,
+ //! Flush instruction cache after a write operation.
+ kFlushAfterWrite = 1,
- //! 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
- //! ignored if the operating system allows to allocate an executable memory
- //! by a different API than `open()` or `shm_open()`. For example on Linux
- //! `memfd_create()` is preferred and on BSDs `shm_open(SHM_ANON, ...)` is
- //! used if SHM_ANON is defined.
- kMappingPreferTmp = 0x80000000u
+ //! Avoid flushing instruction cache after a write operation.
+ kNeverFlush = 2
};
+//! Flushes instruction cache in the given region.
+//!
+//! Only useful on non-x86 architectures, however, it's a good practice to call it on any platform to make your
+//! code more portable.
+ASMJIT_API void flushInstructionCache(void* p, size_t size) noexcept;
+
//! Virtual memory information.
struct Info {
//! Virtual memory page size.
@@ -80,61 +49,274 @@ struct Info {
uint32_t pageGranularity;
};
-//! Dual memory mapping used to map an anonymous memory into two memory regions
-//! where one region is read-only, but executable, and the second region is
-//! read+write, but not executable. Please see \ref VirtMem::allocDualMapping()
-//! for more details.
-struct DualMapping {
- //! Pointer to data with 'Read' or 'Read+Execute' access.
- void* ro;
- //! Pointer to data with 'Read-Write' access, but never 'Write+Execute'.
- void* rw;
-};
-
//! 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).
+//! Returns the size of the smallest large page supported.
+//!
+//! AsmJit only uses the smallest large page at the moment as these are usually perfectly sized for executable
+//! memory allocation (standard size is 2MB, but different sizes are possible).
//!
-//! \note `size` should be aligned to a 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;
+//! Returns either the detected large page size or 0, if large page support is either not supported by AsmJit
+//! or not accessible to the process.
+ASMJIT_API size_t largePageSize() noexcept;
+
+//! Virtual memory access and mmap-specific flags.
+enum class MemoryFlags : uint32_t {
+ //! No flags.
+ kNone = 0,
+
+ //! Memory is readable.
+ kAccessRead = 0x00000001u,
+
+ //! Memory is writable.
+ kAccessWrite = 0x00000002u,
+
+ //! Memory is executable.
+ kAccessExecute = 0x00000004u,
+
+ //! 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 (introduced by Mojave), which allows JIT code to be
+ //! executed in a MAC bundle.
+ //!
+ //! 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.
+ //! When a process uses `fork()` the child process has no access to the pages mapped with `MAP_JIT`.
+ kMMapEnableMapJit = 0x00000010u,
+
+ //! Pass `PROT_MAX(PROT_READ)` or `PROT_MPROTECT(PROT_READ)` to `mmap()` on platforms that support it.
+ //!
+ //! 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,
+
+ //! Pass `PROT_MAX(PROT_WRITE)` or `PROT_MPROTECT(PROT_WRITE)` to `mmap()` on platforms that support it.
+ //!
+ //! 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,
+
+ //! Pass `PROT_MAX(PROT_EXEC)` or `PROT_MPROTECT(PROT_EXEC)` to `mmap()` on platforms that support it.
+ //!
+ //! 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,
+
+ //! 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,
+
+ //! Use `MAP_SHARED` when calling mmap().
+ //!
+ //! \note In some cases `MAP_SHARED` may be set automatically. For example, some dual mapping implementations must
+ //! use `MAP_SHARED` instead of `MAP_PRIVATE` to ensure that the OS would not apply copy on write on RW page, which
+ //! would cause RX page not having the updated content.
+ kMapShared = 0x00000100u,
+
+ //! Request large memory mapped pages.
+ //!
+ //! \remarks If this option is used and large page(s) cannot be mapped, the allocation will fail. Fallback to
+ //! regular pages must be done by the user in this case. Higher level API such as \ref JitAllocator provides an
+ //! additional mechanism to allocate regular page(s) when large page(s) allocation fails.
+ kMMapLargePages = 0x00000200u,
-//! Releases virtual memory previously allocated by \ref VirtMem::alloc() or
-//! \ref VirtMem::allocDualMapping().
+ //! 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 ignored if the
+ //! operating system allows to allocate an executable memory by a different API than `open()` or `shm_open()`. For
+ //! example on Linux `memfd_create()` is preferred and on BSDs `shm_open(SHM_ANON, ...)` is used if SHM_ANON is
+ //! defined.
+ //!
+ //! \note This flag can only be used with \ref VirtMem::alloc().
+ kMappingPreferTmp = 0x80000000u
+};
+ASMJIT_DEFINE_ENUM_FLAGS(MemoryFlags)
+
+//! Allocates virtual memory by either using `mmap()` (POSIX) or `VirtualAlloc()` (Windows).
//!
-//! \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.
+//! \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, MemoryFlags flags) noexcept;
+
+//! 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 it's implemented differently.
ASMJIT_API Error release(void* p, size_t size) noexcept;
-//! A cross-platform wrapper around `mprotect()` (POSIX) and `VirtualProtect`
-//! (Windows).
-ASMJIT_API Error protect(void* p, size_t size, uint32_t flags) noexcept;
+//! A cross-platform wrapper around `mprotect()` (POSIX) and `VirtualProtect()` (Windows).
+ASMJIT_API Error protect(void* p, size_t size, MemoryFlags flags) noexcept;
-//! Allocates virtual memory and creates two views of it where the first view
-//! has no write access. This is an addition to the API that should be used
-//! in cases in which the operating system either enforces W^X security policy
-//! or the application wants to use this policy by default to improve security
-//! and prevent an accidental (or purposed) self-modifying code.
+//! Dual memory mapping used to map an anonymous memory into two memory regions where one region is read-only, but
+//! executable, and the second region is read+write, but not executable. See \ref VirtMem::allocDualMapping() for
+//! more details.
+struct DualMapping {
+ //! Pointer to data with 'Read+Execute' access (this memory is not writable).
+ void* rx;
+ //! Pointer to data with 'Read+Write' access (this memory is not executable).
+ void* rw;
+};
+
+//! Allocates virtual memory and creates two views of it where the first view has no write access. This is an addition
+//! to the API that should be used in cases in which the operating system either enforces W^X security policy or the
+//! application wants to use this policy by default to improve security and prevent an accidental (or purposed)
+//! self-modifying code.
//!
-//! The memory returned in the `dm` are two independent mappings of the same
-//! shared memory region. You must use \ref VirtMem::releaseDualMapping() to
-//! release it when it's no longer needed. Never use `VirtMem::release()` to
-//! release the memory returned by `allocDualMapping()` as that would fail on
-//! Windows.
+//! The memory returned in the `dm` are two independent mappings of the same shared memory region. You must use
+//! \ref VirtMem::releaseDualMapping() to release it when it's no longer needed. Never use `VirtMem::release()` to
+//! release the memory returned by `allocDualMapping()` as that would fail on Windows.
//!
//! \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, MemoryFlags 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;
+//! Hardened runtime flags.
+enum class HardenedRuntimeFlags : uint32_t {
+ //! No flags.
+ kNone = 0,
+
+ //! Hardened runtime is enabled - it's not possible to have "Write & Execute" memory protection. The runtime
+ //! enforces W^X (either write or execute).
+ //!
+ //! \note If the runtime is hardened it means that an operating system specific protection is used. For example
+ //! on Apple OSX it's possible to allocate memory with MAP_JIT flag and then use `pthread_jit_write_protect_np()`
+ //! to temporarily swap access permissions for the current thread. Dual mapping is also a possibility on X86/X64
+ //! architecture.
+ kEnabled = 0x00000001u,
+
+ //! Read+Write+Execute can only be allocated with MAP_JIT flag (Apple specific, only available on Apple platforms).
+ kMapJit = 0x00000002u,
+
+ //! Read+Write+Execute can be allocated with dual mapping approach (one region with RW and the other with RX).
+ kDualMapping = 0x00000004u
+};
+ASMJIT_DEFINE_ENUM_FLAGS(HardenedRuntimeFlags)
+
+//! Hardened runtime information.
+struct HardenedRuntimeInfo {
+ //! \name Members
+ //! \{
+
+ //! Hardened runtime flags.
+ HardenedRuntimeFlags flags;
+
+ //! \}
+
+ //! \name Accessors
+ //! \{
+
+ //! Tests whether the hardened runtime `flag` is set.
+ ASMJIT_INLINE_NODEBUG bool hasFlag(HardenedRuntimeFlags flag) const noexcept { return Support::test(flags, flag); }
+
+ //! \}
+};
+
+//! Returns runtime features provided by the OS.
+ASMJIT_API HardenedRuntimeInfo hardenedRuntimeInfo() noexcept;
+
+//! Values that can be used with `protectJitMemory()` function.
+enum class ProtectJitAccess : uint32_t {
+ //! Protect JIT memory with Read+Write permissions.
+ kReadWrite = 0,
+ //! Protect JIT memory with Read+Execute permissions.
+ kReadExecute = 1
+};
+
+//! Protects access of memory mapped with MAP_JIT flag for the current thread.
+//!
+//! \note This feature is only available on Apple hardware (AArch64) at the moment and uses a non-portable
+//! `pthread_jit_write_protect_np()` call when available.
+//!
+//! This function must be called before and after a memory mapped with MAP_JIT flag is modified. Example:
+//!
+//! ```
+//! void* codePtr = ...;
+//! size_t codeSize = ...;
+//!
+//! VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadWrite);
+//! memcpy(codePtr, source, codeSize);
+//! VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadExecute);
+//! VirtMem::flushInstructionCache(codePtr, codeSize);
+//! ```
+//!
+//! See \ref ProtectJitReadWriteScope, which makes it simpler than the code above.
+ASMJIT_API void protectJitMemory(ProtectJitAccess access) noexcept;
+
+//! JIT protection scope that prepares the given memory block to be written to in the current thread.
+//!
+//! It calls `VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadWrite)` at construction time and
+//! `VirtMem::protectJitMemory(VirtMem::ProtectJitAccess::kReadExecute)` combined with `flushInstructionCache()`
+//! in destructor. The purpose of this class is to make writing to JIT memory easier.
+class ProtectJitReadWriteScope {
+public:
+ ASMJIT_NONCOPYABLE(ProtectJitReadWriteScope)
+
+ //! \name Members
+ //! \{
+
+ void* _rxPtr;
+ size_t _size;
+ CachePolicy _policy;
+
+ //! \}
+
+ //! \name Construction & Destruction
+ //! \{
+
+ //! Makes the given memory block RW protected.
+ ASMJIT_FORCE_INLINE ProtectJitReadWriteScope(
+ void* rxPtr,
+ size_t size,
+ CachePolicy policy = CachePolicy::kDefault) noexcept
+ : _rxPtr(rxPtr),
+ _size(size),
+ _policy(policy) {
+ protectJitMemory(ProtectJitAccess::kReadWrite);
+ }
+
+ //! Makes the memory block RX protected again and flushes instruction cache.
+ ASMJIT_FORCE_INLINE ~ProtectJitReadWriteScope() noexcept {
+ protectJitMemory(ProtectJitAccess::kReadExecute);
+
+ if (_policy != CachePolicy::kNeverFlush)
+ flushInstructionCache(_rxPtr, _size);
+ }
+
+ //! \}
+};
+
} // VirtMem
//! \}