// Copyright 2013 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_H_ #define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_H_ #include #include #include "base/allocator/partition_allocator/page_allocator_constants.h" #include "base/allocator/partition_allocator/partition_alloc_base/compiler_specific.h" #include "base/allocator/partition_allocator/partition_alloc_base/component_export.h" #include "base/allocator/partition_allocator/partition_alloc_buildflags.h" #include "build/build_config.h" namespace partition_alloc { struct PageAccessibilityConfiguration { enum Permissions { kInaccessible, kRead, kReadWrite, // This flag is mapped to kReadWrite on systems that // don't support MTE. kReadWriteTagged, // This flag is mapped to kReadExecute on systems // that don't support Arm's BTI. kReadExecuteProtected, kReadExecute, // This flag is deprecated and will go away soon. // TODO(bbudge) Remove this as soon as V8 doesn't need RWX pages. kReadWriteExecute, }; #if BUILDFLAG(ENABLE_PKEYS) constexpr explicit PageAccessibilityConfiguration(Permissions permissions) : permissions(permissions), pkey(0) {} constexpr PageAccessibilityConfiguration(Permissions permissions, int pkey) : permissions(permissions), pkey(pkey) {} #else constexpr explicit PageAccessibilityConfiguration(Permissions permissions) : permissions(permissions) {} #endif // BUILDFLAG(ENABLE_PKEYS) Permissions permissions; #if BUILDFLAG(ENABLE_PKEYS) // Tag the page with a Memory Protection Key. Use 0 for none. int pkey; #endif // BUILDFLAG(ENABLE_PKEYS) }; // Use for De/RecommitSystemPages API. enum class PageAccessibilityDisposition { // Enforces permission update (Decommit will set to // PageAccessibilityConfiguration::kInaccessible; // Recommit will set to whatever was requested, other than // PageAccessibilityConfiguration::kInaccessible). kRequireUpdate, // Will not update permissions, if the platform supports that (POSIX & Fuchsia // only). kAllowKeepForPerf, }; // macOS supports tagged memory regions, to help in debugging. On Android, // these tags are used to name anonymous mappings. enum class PageTag { kFirst = 240, // Minimum tag value. kSimulation = 251, // Memory simulator tool. kBlinkGC = 252, // Blink GC pages. kPartitionAlloc = 253, // PartitionAlloc, no matter the partition. kChromium = 254, // Chromium page. kV8 = 255, // V8 heap pages. kLast = kV8 // Maximum tag value. }; // See // https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/osfmk/mach/vm_statistics.h#L687 static_assert( static_cast(PageTag::kLast) < 256, "Tags are only 1 byte long on macOS, see vm_statistics.h in XNU."); PA_COMPONENT_EXPORT(PARTITION_ALLOC) uintptr_t NextAlignedWithOffset(uintptr_t ptr, uintptr_t alignment, uintptr_t requested_offset); // Allocates one or more pages. // // The requested |address| is just a hint; the actual address returned may // differ. The returned address will be aligned to |align_offset| modulo |align| // bytes. // // |length|, |align| and |align_offset| are in bytes, and must be a multiple of // |PageAllocationGranularity()|. |length| and |align| must be non-zero. // |align_offset| must be less than |align|. |align| must be a power of two. // // If |address| is 0/nullptr, then a suitable and randomized address will be // chosen automatically. // // |accessibility| controls the permission of the allocated pages. // PageAccessibilityConfiguration::kInaccessible means uncommitted. // // |page_tag| is used on some platforms to identify the source of the // allocation. Use PageTag::kChromium as a catch-all category. // // |file_descriptor_for_shared_alloc| is only used in mapping the shadow // pools to the same physical address as the real one in // PartitionAddressSpace::Init(). It should be ignored in other cases. // // This call will return 0/nullptr if the allocation cannot be satisfied. PA_COMPONENT_EXPORT(PARTITION_ALLOC) uintptr_t AllocPages(size_t length, size_t align, PageAccessibilityConfiguration accessibility, PageTag page_tag, int file_descriptor_for_shared_alloc = -1); PA_COMPONENT_EXPORT(PARTITION_ALLOC) uintptr_t AllocPages(uintptr_t address, size_t length, size_t align, PageAccessibilityConfiguration accessibility, PageTag page_tag); PA_COMPONENT_EXPORT(PARTITION_ALLOC) void* AllocPages(void* address, size_t length, size_t align, PageAccessibilityConfiguration accessibility, PageTag page_tag); PA_COMPONENT_EXPORT(PARTITION_ALLOC) uintptr_t AllocPagesWithAlignOffset( uintptr_t address, size_t length, size_t align, size_t align_offset, PageAccessibilityConfiguration page_accessibility, PageTag page_tag, int file_descriptor_for_shared_alloc = -1); // Frees one or more pages starting at |address| and continuing for |length| // bytes. // // |address| and |length| must match a previous call to |AllocPages|. Therefore, // |address| must be aligned to |PageAllocationGranularity()| bytes, and // |length| must be a multiple of |PageAllocationGranularity()|. PA_COMPONENT_EXPORT(PARTITION_ALLOC) void FreePages(uintptr_t address, size_t length); PA_COMPONENT_EXPORT(PARTITION_ALLOC) void FreePages(void* address, size_t length); // Marks one or more system pages, starting at |address| with the given // |page_accessibility|. |length| must be a multiple of |SystemPageSize()| // bytes. // // Returns true if the permission change succeeded. In most cases you must // |CHECK| the result. [[nodiscard]] PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool TrySetSystemPagesAccess( uintptr_t address, size_t length, PageAccessibilityConfiguration page_accessibility); [[nodiscard]] PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool TrySetSystemPagesAccess( void* address, size_t length, PageAccessibilityConfiguration page_accessibility); // Marks one or more system pages, starting at |address| with the given // |page_accessibility|. |length| must be a multiple of |SystemPageSize()| // bytes. // // Performs a CHECK that the operation succeeds. PA_COMPONENT_EXPORT(PARTITION_ALLOC) void SetSystemPagesAccess(uintptr_t address, size_t length, PageAccessibilityConfiguration page_accessibility); PA_COMPONENT_EXPORT(PARTITION_ALLOC) void SetSystemPagesAccess(void* address, size_t length, PageAccessibilityConfiguration page_accessibility); // Decommits one or more system pages starting at |address| and continuing for // |length| bytes. |address| and |length| must be aligned to a system page // boundary. // // This API will crash if the operation cannot be performed! // // If disposition is PageAccessibilityDisposition::kRequireUpdate (recommended), // the decommitted pages will be made inaccessible before the call returns. // While it is always a programming error to access decommitted pages without // first recommitting them, callers may use // PageAccessibilityDisposition::kAllowKeepForPerf to allow the implementation // to skip changing permissions (use with care), for performance reasons (see // crrev.com/c/2567282 and crrev.com/c/2563038 for perf regressions encountered // in the past). Implementations may choose to always modify permissions, hence // accessing those pages may or may not trigger a fault. // // Decommitting means that physical resources (RAM or swap/pagefile) backing the // allocated virtual address range may be released back to the system, but the // address space is still allocated to the process (possibly using up page table // entries or other accounting resources). There is no guarantee that the pages // are zeroed, unless |DecommittedMemoryIsAlwaysZeroed()| is true. // // This operation may not be atomic on some platforms. // // Note: "Committed memory" is a Windows Memory Subsystem concept that ensures // processes will not fault when touching a committed memory region. There is // no analogue in the POSIX & Fuchsia memory API where virtual memory pages are // best-effort allocated resources on the first touch. If // PageAccessibilityDisposition::kRequireUpdate disposition is used, this API // behaves in a platform-agnostic way by simulating the Windows "decommit" state // by both discarding the region (allowing the OS to avoid swap operations) // *and* changing the page protections so accesses fault. PA_COMPONENT_EXPORT(PARTITION_ALLOC) void DecommitSystemPages( uintptr_t address, size_t length, PageAccessibilityDisposition accessibility_disposition); PA_COMPONENT_EXPORT(PARTITION_ALLOC) void DecommitSystemPages( void* address, size_t length, PageAccessibilityDisposition accessibility_disposition); // Decommits one or more system pages starting at |address| and continuing for // |length| bytes. |address| and |length| must be aligned to a system page // boundary. // // In contrast to |DecommitSystemPages|, this API guarantees that the pages are // zeroed and will always mark the region as inaccessible (the equivalent of // setting them to PageAccessibilityConfiguration::kInaccessible). // // This API will crash if the operation cannot be performed. PA_COMPONENT_EXPORT(PARTITION_ALLOC) void DecommitAndZeroSystemPages(uintptr_t address, size_t length); PA_COMPONENT_EXPORT(PARTITION_ALLOC) void DecommitAndZeroSystemPages(void* address, size_t length); // Whether decommitted memory is guaranteed to be zeroed when it is // recommitted. Do not assume that this will not change over time. constexpr PA_COMPONENT_EXPORT( PARTITION_ALLOC) bool DecommittedMemoryIsAlwaysZeroed() { #if BUILDFLAG(IS_APPLE) return false; #else return true; #endif } // (Re)Commits one or more system pages, starting at |address| and continuing // for |length| bytes with the given |page_accessibility| (must not be // PageAccessibilityConfiguration::kInaccessible). |address| and |length| // must be aligned to a system page boundary. // // This API will crash if the operation cannot be performed! // // If disposition is PageAccessibilityConfiguration::kRequireUpdate, the calls // updates the pages to |page_accessibility|. This can be used regardless of // what disposition was used to decommit the pages. // PageAccessibilityConfiguration::kAllowKeepForPerf allows the implementation // to leave the page permissions, if that improves performance. This option can // only be used if the pages were previously accessible and decommitted with // that same option. // // The memory will be zeroed when it is committed for the first time. However, // there is no such guarantee when memory is recommitted, unless // |DecommittedMemoryIsAlwaysZeroed()| is true. // // This operation may not be atomic on some platforms. PA_COMPONENT_EXPORT(PARTITION_ALLOC) void RecommitSystemPages( uintptr_t address, size_t length, PageAccessibilityConfiguration page_accessibility, PageAccessibilityDisposition accessibility_disposition); // Like RecommitSystemPages(), but returns false instead of crashing. [[nodiscard]] PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool TryRecommitSystemPages( uintptr_t address, size_t length, PageAccessibilityConfiguration page_accessibility, PageAccessibilityDisposition accessibility_disposition); // Discard one or more system pages starting at |address| and continuing for // |length| bytes. |length| must be a multiple of |SystemPageSize()|. // // Discarding is a hint to the system that the page is no longer required. The // hint may: // - Do nothing. // - Discard the page immediately, freeing up physical pages. // - Discard the page at some time in the future in response to memory // pressure. // // Only committed pages should be discarded. Discarding a page does not decommit // it, and it is valid to discard an already-discarded page. A read or write to // a discarded page will not fault. // // Reading from a discarded page may return the original page content, or a page // full of zeroes. // // Writing to a discarded page is the only guaranteed way to tell the system // that the page is required again. Once written to, the content of the page is // guaranteed stable once more. After being written to, the page content may be // based on the original page content, or a page of zeroes. PA_COMPONENT_EXPORT(PARTITION_ALLOC) void DiscardSystemPages(uintptr_t address, size_t length); PA_COMPONENT_EXPORT(PARTITION_ALLOC) void DiscardSystemPages(void* address, size_t length); // Rounds up |address| to the next multiple of |SystemPageSize()|. Returns // 0 for an |address| of 0. PA_ALWAYS_INLINE PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR uintptr_t RoundUpToSystemPage(uintptr_t address) { return (address + internal::SystemPageOffsetMask()) & internal::SystemPageBaseMask(); } // Rounds down |address| to the previous multiple of |SystemPageSize()|. Returns // 0 for an |address| of 0. PA_ALWAYS_INLINE PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR uintptr_t RoundDownToSystemPage(uintptr_t address) { return address & internal::SystemPageBaseMask(); } // Rounds up |address| to the next multiple of |PageAllocationGranularity()|. // Returns 0 for an |address| of 0. PA_ALWAYS_INLINE PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR uintptr_t RoundUpToPageAllocationGranularity(uintptr_t address) { return (address + internal::PageAllocationGranularityOffsetMask()) & internal::PageAllocationGranularityBaseMask(); } // Rounds down |address| to the previous multiple of // |PageAllocationGranularity()|. Returns 0 for an |address| of 0. PA_ALWAYS_INLINE PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR uintptr_t RoundDownToPageAllocationGranularity(uintptr_t address) { return address & internal::PageAllocationGranularityBaseMask(); } // Reserves (at least) |size| bytes of address space, aligned to // |PageAllocationGranularity()|. This can be called early on to make it more // likely that large allocations will succeed. Returns true if the reservation // succeeded, false if the reservation failed or a reservation was already made. PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool ReserveAddressSpace(size_t size); // Releases any reserved address space. |AllocPages| calls this automatically on // an allocation failure. External allocators may also call this on failure. // // Returns true when an existing reservation was released. PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool ReleaseReservation(); // Returns true if there is currently an address space reservation. PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool HasReservationForTesting(); // Returns |errno| (POSIX) or the result of |GetLastError| (Windows) when |mmap| // (POSIX) or |VirtualAlloc| (Windows) fails. PA_COMPONENT_EXPORT(PARTITION_ALLOC) uint32_t GetAllocPageErrorCode(); // Returns the total amount of mapped pages from all clients of // PageAllocator. These pages may or may not be committed. This is mostly useful // to assess address space pressure. PA_COMPONENT_EXPORT(PARTITION_ALLOC) size_t GetTotalMappedSize(); #if BUILDFLAG(IS_WIN) // Sets whether to retry the allocation of pages when a commit failure // happens. This doesn't cover cases where the system is out of address space, // or reaches another limit. PA_COMPONENT_EXPORT(PARTITION_ALLOC) void SetRetryOnCommitFailure(bool retry_on_commit_failure); bool GetRetryOnCommitFailure(); #endif // BUILDFLAG(IS_WIN) } // namespace partition_alloc #endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_H_