blob: 5065d5c8ae233e1a53270814bcf8e4a4fb2d0a6f [file] [log] [blame]
// 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.
#include "partition_alloc/page_allocator.h"
#include <atomic>
#include <bit>
#include <cstdint>
#include "build/build_config.h"
#include "partition_alloc/address_space_randomization.h"
#include "partition_alloc/page_allocator_internal.h"
#include "partition_alloc/partition_alloc_base/thread_annotations.h"
#include "partition_alloc/partition_alloc_check.h"
#include "partition_alloc/partition_lock.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif
#if BUILDFLAG(IS_WIN)
#include "partition_alloc/page_allocator_internals_win.h"
#elif BUILDFLAG(IS_POSIX)
#include "partition_alloc/page_allocator_internals_posix.h"
#elif BUILDFLAG(IS_FUCHSIA)
#include "partition_alloc/page_allocator_internals_fuchsia.h"
#else
#error Platform not supported.
#endif
namespace partition_alloc {
namespace {
internal::Lock g_reserve_lock;
// We may reserve/release address space on different threads.
internal::Lock& GetReserveLock() {
return g_reserve_lock;
}
std::atomic<size_t> g_total_mapped_address_space;
// We only support a single block of reserved address space.
uintptr_t s_reservation_address PA_GUARDED_BY(GetReserveLock()) = 0;
size_t s_reservation_size PA_GUARDED_BY(GetReserveLock()) = 0;
uintptr_t AllocPagesIncludingReserved(
uintptr_t address,
size_t length,
PageAccessibilityConfiguration accessibility,
PageTag page_tag,
int file_descriptor_for_shared_alloc = -1) {
uintptr_t ret =
internal::SystemAllocPages(address, length, accessibility, page_tag,
file_descriptor_for_shared_alloc);
if (!ret) {
const bool cant_alloc_length = internal::kHintIsAdvisory || !address;
if (cant_alloc_length) {
// The system cannot allocate |length| bytes. Release any reserved address
// space and try once more.
ReleaseReservation();
ret = internal::SystemAllocPages(address, length, accessibility, page_tag,
file_descriptor_for_shared_alloc);
}
}
return ret;
}
// Trims memory at |base_address| to given |trim_length| and |alignment|.
//
// On failure, on Windows, this function returns 0 and frees memory at
// |base_address|.
uintptr_t TrimMapping(uintptr_t base_address,
size_t base_length,
size_t trim_length,
uintptr_t alignment,
uintptr_t alignment_offset,
PageAccessibilityConfiguration accessibility) {
PA_DCHECK(base_length >= trim_length);
PA_DCHECK(std::has_single_bit(alignment));
PA_DCHECK(alignment_offset < alignment);
uintptr_t new_base =
NextAlignedWithOffset(base_address, alignment, alignment_offset);
PA_DCHECK(new_base >= base_address);
size_t pre_slack = new_base - base_address;
size_t post_slack = base_length - pre_slack - trim_length;
PA_DCHECK(base_length == trim_length || pre_slack || post_slack);
PA_DCHECK(pre_slack < base_length);
PA_DCHECK(post_slack < base_length);
return internal::TrimMappingInternal(base_address, base_length, trim_length,
accessibility, pre_slack, post_slack);
}
} // namespace
// Align |address| up to the closest, non-smaller address, that gives
// |requested_offset| remainder modulo |alignment|.
//
// Examples for alignment=1024 and requested_offset=64:
// 64 -> 64
// 65 -> 1088
// 1024 -> 1088
// 1088 -> 1088
// 1089 -> 2112
// 2048 -> 2112
uintptr_t NextAlignedWithOffset(uintptr_t address,
uintptr_t alignment,
uintptr_t requested_offset) {
PA_DCHECK(std::has_single_bit(alignment));
PA_DCHECK(requested_offset < alignment);
uintptr_t actual_offset = address & (alignment - 1);
uintptr_t new_address;
if (actual_offset <= requested_offset) {
new_address = address + requested_offset - actual_offset;
} else {
new_address = address + alignment + requested_offset - actual_offset;
}
PA_DCHECK(new_address >= address);
PA_DCHECK(new_address - address < alignment);
PA_DCHECK(new_address % alignment == requested_offset);
return new_address;
}
namespace internal {
uintptr_t SystemAllocPages(uintptr_t hint,
size_t length,
PageAccessibilityConfiguration accessibility,
PageTag page_tag,
int file_descriptor_for_shared_alloc) {
PA_DCHECK(!(length & internal::PageAllocationGranularityOffsetMask()));
PA_DCHECK(!(hint & internal::PageAllocationGranularityOffsetMask()));
uintptr_t ret = internal::SystemAllocPagesInternal(
hint, length, accessibility, page_tag, file_descriptor_for_shared_alloc);
if (ret) {
g_total_mapped_address_space.fetch_add(length, std::memory_order_relaxed);
}
return ret;
}
} // namespace internal
uintptr_t AllocPages(size_t length,
size_t align,
PageAccessibilityConfiguration accessibility,
PageTag page_tag,
int file_descriptor_for_shared_alloc) {
return AllocPagesWithAlignOffset(0, length, align, 0, accessibility, page_tag,
file_descriptor_for_shared_alloc);
}
uintptr_t AllocPages(uintptr_t address,
size_t length,
size_t align,
PageAccessibilityConfiguration accessibility,
PageTag page_tag) {
return AllocPagesWithAlignOffset(address, length, align, 0, accessibility,
page_tag);
}
void* AllocPages(void* address,
size_t length,
size_t align,
PageAccessibilityConfiguration accessibility,
PageTag page_tag) {
return reinterpret_cast<void*>(
AllocPages(reinterpret_cast<uintptr_t>(address), length, align,
accessibility, page_tag));
}
uintptr_t AllocPagesWithAlignOffset(
uintptr_t address,
size_t length,
size_t align,
size_t align_offset,
PageAccessibilityConfiguration accessibility,
PageTag page_tag,
int file_descriptor_for_shared_alloc) {
PA_DCHECK(length >= internal::PageAllocationGranularity());
PA_DCHECK(!(length & internal::PageAllocationGranularityOffsetMask()));
PA_DCHECK(align >= internal::PageAllocationGranularity());
// Alignment must be power of 2 for masking math to work.
PA_DCHECK(std::has_single_bit(align));
PA_DCHECK(align_offset < align);
PA_DCHECK(!(align_offset & internal::PageAllocationGranularityOffsetMask()));
PA_DCHECK(!(address & internal::PageAllocationGranularityOffsetMask()));
uintptr_t align_offset_mask = align - 1;
uintptr_t align_base_mask = ~align_offset_mask;
PA_DCHECK(!address || (address & align_offset_mask) == align_offset);
// If the client passed null as the address, choose a good one.
if (!address) {
address = (GetRandomPageBase() & align_base_mask) + align_offset;
}
// First try to force an exact-size, aligned allocation from our random base.
#if defined(ARCH_CPU_32_BITS)
// On 32 bit systems, first try one random aligned address, and then try an
// aligned address derived from the value of |ret|.
constexpr int kExactSizeTries = 2;
#else
// On 64 bit systems, try 3 random aligned addresses.
constexpr int kExactSizeTries = 3;
#endif
for (int i = 0; i < kExactSizeTries; ++i) {
uintptr_t ret =
AllocPagesIncludingReserved(address, length, accessibility, page_tag,
file_descriptor_for_shared_alloc);
if (ret) {
// If the alignment is to our liking, we're done.
if ((ret & align_offset_mask) == align_offset) {
return ret;
}
// Free the memory and try again.
FreePages(ret, length);
} else {
// |ret| is null; if this try was unhinted, we're OOM.
if (internal::kHintIsAdvisory || !address) {
return 0;
}
}
#if defined(ARCH_CPU_32_BITS)
// For small address spaces, try the first aligned address >= |ret|. Note
// |ret| may be null, in which case |address| becomes null. If
// |align_offset| is non-zero, this calculation may get us not the first,
// but the next matching address.
address = ((ret + align_offset_mask) & align_base_mask) + align_offset;
#else // defined(ARCH_CPU_64_BITS)
// Keep trying random addresses on systems that have a large address space.
address = NextAlignedWithOffset(GetRandomPageBase(), align, align_offset);
#endif
}
// Make a larger allocation so we can force alignment.
size_t try_length = length + (align - internal::PageAllocationGranularity());
PA_CHECK(try_length >= length);
uintptr_t ret;
do {
// Continue randomizing only on POSIX.
address = internal::kHintIsAdvisory ? GetRandomPageBase() : 0;
ret =
AllocPagesIncludingReserved(address, try_length, accessibility,
page_tag, file_descriptor_for_shared_alloc);
// The retries are for Windows, where a race can steal our mapping on
// resize.
} while (ret && (ret = TrimMapping(ret, try_length, length, align,
align_offset, accessibility)) == 0);
return ret;
}
void FreePages(uintptr_t address, size_t length) {
PA_DCHECK(!(address & internal::PageAllocationGranularityOffsetMask()));
PA_DCHECK(!(length & internal::PageAllocationGranularityOffsetMask()));
internal::FreePagesInternal(address, length);
PA_DCHECK(g_total_mapped_address_space.load(std::memory_order_relaxed) > 0);
g_total_mapped_address_space.fetch_sub(length, std::memory_order_relaxed);
}
void FreePages(void* address, size_t length) {
FreePages(reinterpret_cast<uintptr_t>(address), length);
}
bool TrySetSystemPagesAccess(uintptr_t address,
size_t length,
PageAccessibilityConfiguration accessibility) {
PA_DCHECK(!(length & internal::SystemPageOffsetMask()));
return internal::TrySetSystemPagesAccessInternal(address, length,
accessibility);
}
bool TrySetSystemPagesAccess(void* address,
size_t length,
PageAccessibilityConfiguration accessibility) {
return TrySetSystemPagesAccess(reinterpret_cast<uintptr_t>(address), length,
accessibility);
}
void SetSystemPagesAccess(uintptr_t address,
size_t length,
PageAccessibilityConfiguration accessibility) {
PA_DCHECK(!(length & internal::SystemPageOffsetMask()));
internal::SetSystemPagesAccessInternal(address, length, accessibility);
}
void SetSystemPagesAccess(void* address,
size_t length,
PageAccessibilityConfiguration accessibility) {
SetSystemPagesAccess(reinterpret_cast<uintptr_t>(address), length,
accessibility);
}
void DecommitSystemPages(
uintptr_t address,
size_t length,
PageAccessibilityDisposition accessibility_disposition) {
PA_DCHECK(!(address & internal::SystemPageOffsetMask()));
PA_DCHECK(!(length & internal::SystemPageOffsetMask()));
internal::DecommitSystemPagesInternal(address, length,
accessibility_disposition);
}
void DecommitSystemPages(
void* address,
size_t length,
PageAccessibilityDisposition accessibility_disposition) {
DecommitSystemPages(reinterpret_cast<uintptr_t>(address), length,
accessibility_disposition);
}
void DecommitAndZeroSystemPages(uintptr_t address,
size_t length,
PageTag page_tag) {
PA_DCHECK(!(address & internal::SystemPageOffsetMask()));
PA_DCHECK(!(length & internal::SystemPageOffsetMask()));
internal::DecommitAndZeroSystemPagesInternal(address, length, page_tag);
}
void DecommitAndZeroSystemPages(void* address,
size_t length,
PageTag page_tag) {
DecommitAndZeroSystemPages(reinterpret_cast<uintptr_t>(address), length,
page_tag);
}
void RecommitSystemPages(
uintptr_t address,
size_t length,
PageAccessibilityConfiguration accessibility,
PageAccessibilityDisposition accessibility_disposition) {
PA_DCHECK(!(address & internal::SystemPageOffsetMask()));
PA_DCHECK(!(length & internal::SystemPageOffsetMask()));
PA_DCHECK(accessibility.permissions !=
PageAccessibilityConfiguration::kInaccessible);
internal::RecommitSystemPagesInternal(address, length, accessibility,
accessibility_disposition);
}
bool TryRecommitSystemPages(
uintptr_t address,
size_t length,
PageAccessibilityConfiguration accessibility,
PageAccessibilityDisposition accessibility_disposition) {
// Duplicated because we want errors to be reported at a lower level in the
// crashing case.
PA_DCHECK(!(address & internal::SystemPageOffsetMask()));
PA_DCHECK(!(length & internal::SystemPageOffsetMask()));
PA_DCHECK(accessibility.permissions !=
PageAccessibilityConfiguration::kInaccessible);
return internal::TryRecommitSystemPagesInternal(
address, length, accessibility, accessibility_disposition);
}
void DiscardSystemPages(uintptr_t address, size_t length) {
PA_DCHECK(!(length & internal::SystemPageOffsetMask()));
internal::DiscardSystemPagesInternal(address, length);
}
void DiscardSystemPages(void* address, size_t length) {
DiscardSystemPages(reinterpret_cast<uintptr_t>(address), length);
}
bool ReserveAddressSpace(size_t size) {
// To avoid deadlock, call only SystemAllocPages.
internal::ScopedGuard guard(GetReserveLock());
if (!s_reservation_address) {
uintptr_t mem = internal::SystemAllocPages(
0, size,
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible),
PageTag::kChromium);
if (mem) {
// We guarantee this alignment when reserving address space.
PA_DCHECK(!(mem & internal::PageAllocationGranularityOffsetMask()));
s_reservation_address = mem;
s_reservation_size = size;
return true;
}
}
return false;
}
bool ReleaseReservation() {
// To avoid deadlock, call only FreePages.
internal::ScopedGuard guard(GetReserveLock());
if (!s_reservation_address) {
return false;
}
FreePages(s_reservation_address, s_reservation_size);
s_reservation_address = 0;
s_reservation_size = 0;
return true;
}
bool HasReservationForTesting() {
internal::ScopedGuard guard(GetReserveLock());
return s_reservation_address;
}
uint32_t GetAllocPageErrorCode() {
return internal::s_allocPageErrorCode;
}
size_t GetTotalMappedSize() {
return g_total_mapped_address_space;
}
#if BUILDFLAG(IS_WIN)
namespace {
bool g_retry_on_commit_failure = false;
}
void SetRetryOnCommitFailure(bool retry_on_commit_failure) {
g_retry_on_commit_failure = retry_on_commit_failure;
}
bool GetRetryOnCommitFailure() {
return g_retry_on_commit_failure;
}
#endif
} // namespace partition_alloc