#include "partition_alloc/partition_address_space.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <ostream>
#include <string>
#include "partition_alloc/address_pool_manager.h"
#include "partition_alloc/allocator_config.h"
#include "partition_alloc/build_config.h"
#include "partition_alloc/buildflags.h"
#include "partition_alloc/compressed_pointer.h"
#include "partition_alloc/page_allocator.h"
#include "partition_alloc/page_allocator_internal.h"
#include "partition_alloc/partition_alloc_base/bits.h"
#include "partition_alloc/partition_alloc_base/compiler_specific.h"
#include "partition_alloc/partition_alloc_base/debug/alias.h"
#include "partition_alloc/partition_alloc_base/files/platform_file.h"
#include "partition_alloc/partition_alloc_check.h"
#include "partition_alloc/partition_alloc_config.h"
#include "partition_alloc/partition_alloc_constants.h"
#include "partition_alloc/thread_isolation/thread_isolation.h"
#if PA_BUILDFLAG(IS_IOS)
#include <mach-o/dyld.h>
#endif
#if PA_BUILDFLAG(IS_WIN)
#include <windows.h>
#endif
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
#include <sys/mman.h>
#endif
namespace partition_alloc::internal {
#if PA_BUILDFLAG(HAS_64_BIT_POINTERS)
namespace {
#if PA_BUILDFLAG(IS_WIN)
PA_NOINLINE void HandlePoolAllocFailureOutOfVASpace() {
PA_NO_CODE_FOLDING();
PA_CHECK(false);
}
PA_NOINLINE void HandlePoolAllocFailureOutOfCommitCharge() {
PA_NO_CODE_FOLDING();
PA_CHECK(false);
}
#endif
PA_NOINLINE void HandlePoolAllocFailure() {
PA_NO_CODE_FOLDING();
uint32_t alloc_page_error_code = GetAllocPageErrorCode();
PA_DEBUG_DATA_ON_STACK("error", static_cast<size_t>(alloc_page_error_code));
#if PA_BUILDFLAG(IS_WIN)
if (alloc_page_error_code == ERROR_NOT_ENOUGH_MEMORY) {
HandlePoolAllocFailureOutOfVASpace();
} else if (alloc_page_error_code == ERROR_COMMITMENT_LIMIT ||
alloc_page_error_code == ERROR_COMMITMENT_MINIMUM) {
HandlePoolAllocFailureOutOfCommitCharge();
} else
#endif {
PA_CHECK(false);
}
}
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR size_t
MetadataInnerOffset(pool_handle pool) {
PA_DCHECK(kRegularPoolHandle <= pool && pool < kMaxPoolHandle);
return SystemPageSize() * (2 * (pool - kRegularPoolHandle) + 1);
}
#endif
}
PA_CONSTINIT PartitionAddressSpace::PoolSetup PartitionAddressSpace::setup_;
#if PA_CONFIG(ENABLE_USER_SPACE_ZERO_SEGMENT)
size_t PartitionAddressSpace::zero_segment_size_ = 0;
#endif
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
PA_CONSTINIT std::array<std::ptrdiff_t, kMaxPoolHandle>
PartitionAddressSpace::offsets_to_metadata_ = {
0, 0, 0, 0,
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
0,
#endif };
uintptr_t PartitionAddressSpace::metadata_region_start_ =
PartitionAddressSpace::kUninitializedPoolBaseAddress;
#if PA_CONFIG(DYNAMICALLY_SELECT_POOL_SIZE)
size_t PartitionAddressSpace::metadata_region_size_ = 0;
#endif #endif
#if PA_CONFIG(DYNAMICALLY_SELECT_POOL_SIZE)
#if !PA_BUILDFLAG(IS_IOS)
#error Dynamic pool size is only supported on iOS.
#endif
bool PartitionAddressSpace::IsIOSTestProcess() {
constexpr size_t path_buffer_size = 8192;
char executable_path[path_buffer_size];
uint32_t executable_length = path_buffer_size;
int rv = _NSGetExecutablePath(executable_path, &executable_length);
PA_CHECK(!rv);
size_t executable_path_length =
std::char_traits<char>::length(executable_path);
auto has_suffix = [&](const char* suffix) -> bool {
size_t suffix_length = std::char_traits<char>::length(suffix);
if (executable_path_length < suffix_length) {
return false;
}
return std::char_traits<char>::compare(
PA_UNSAFE_TODO(executable_path +
(executable_path_length - suffix_length)),
suffix, suffix_length) == 0;
};
return has_suffix("Runner") || has_suffix("ios_web_view_inttests");
}
#endif
void PartitionAddressSpace::Init() {
if (IsInitialized()) {
return;
}
#if PA_CONFIG(ENABLE_USER_SPACE_ZERO_SEGMENT)
InitZeroSegment();
#endif
const size_t core_pool_size = CorePoolSize();
size_t glued_pool_sizes = core_pool_size * 2;
setup_.regular_pool_base_address_ =
AllocPages(glued_pool_sizes, glued_pool_sizes,
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible),
PageTag::kPartitionAlloc);
#if PA_BUILDFLAG(IS_ANDROID)
if (setup_.regular_pool_base_address_ == 0x400000000) {
uintptr_t new_base_address =
AllocPages(glued_pool_sizes, glued_pool_sizes,
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible),
PageTag::kPartitionAlloc);
FreePages(setup_.regular_pool_base_address_, glued_pool_sizes);
setup_.regular_pool_base_address_ = new_base_address;
}
#endif if (!setup_.regular_pool_base_address_) {
HandlePoolAllocFailure();
}
setup_.brp_pool_base_address_ =
setup_.regular_pool_base_address_ + core_pool_size;
#if PA_CONFIG(DYNAMICALLY_SELECT_POOL_SIZE)
setup_.core_pool_base_mask_ = ~(core_pool_size - 1);
setup_.glued_pools_base_mask_ = setup_.core_pool_base_mask_ << 1;
#endif
AddressPoolManager::GetInstance().Add(
kRegularPoolHandle, setup_.regular_pool_base_address_, core_pool_size);
AddressPoolManager::GetInstance().Add(
kBRPPoolHandle, setup_.brp_pool_base_address_, core_pool_size);
PA_DCHECK(!(setup_.regular_pool_base_address_ & (core_pool_size - 1)));
PA_DCHECK(!(setup_.brp_pool_base_address_ & (core_pool_size - 1)));
PA_DCHECK(!(setup_.regular_pool_base_address_ & (glued_pool_sizes - 1)));
PA_DCHECK(!IsInRegularPool(setup_.regular_pool_base_address_ - 1));
PA_DCHECK(IsInRegularPool(setup_.regular_pool_base_address_));
PA_DCHECK(
IsInRegularPool(setup_.regular_pool_base_address_ + core_pool_size - 1));
PA_DCHECK(
!IsInRegularPool(setup_.regular_pool_base_address_ + core_pool_size));
PA_DCHECK(!IsInBRPPool(setup_.brp_pool_base_address_ - 1));
PA_DCHECK(IsInBRPPool(setup_.brp_pool_base_address_));
PA_DCHECK(IsInBRPPool(setup_.brp_pool_base_address_ + core_pool_size - 1));
PA_DCHECK(!IsInBRPPool(setup_.brp_pool_base_address_ + core_pool_size));
PA_DCHECK(!IsInCorePools(setup_.regular_pool_base_address_ - 1));
PA_DCHECK(IsInCorePools(setup_.regular_pool_base_address_));
PA_DCHECK(
IsInCorePools(setup_.regular_pool_base_address_ + core_pool_size - 1));
PA_DCHECK(IsInCorePools(setup_.regular_pool_base_address_ + core_pool_size));
PA_DCHECK(IsInCorePools(setup_.brp_pool_base_address_ - 1));
PA_DCHECK(IsInCorePools(setup_.brp_pool_base_address_));
PA_DCHECK(IsInCorePools(setup_.brp_pool_base_address_ + core_pool_size - 1));
PA_DCHECK(!IsInCorePools(setup_.brp_pool_base_address_ + core_pool_size));
#if PA_BUILDFLAG(ENABLE_POINTER_COMPRESSION)
CompressedPointerBaseGlobal::SetBase(setup_.regular_pool_base_address_);
#endif
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
InitMetadataRegionAndOffsets();
#endif }
#if PA_CONFIG(ENABLE_USER_SPACE_ZERO_SEGMENT)
void PartitionAddressSpace::InitZeroSegment() {
if (zero_segment_size_) {
return;
}
zero_segment_size_ = GetZeroSegmentSizeFromOS();
const size_t min_zero_segment_size =
static_cast<size_t>(PA_CONFIG(USER_SPACE_ZERO_SEGMENT_SIZE_MB)) * 1024 *
1024;
if (zero_segment_size_ >= min_zero_segment_size) {
return;
}
const size_t allocation_size = min_zero_segment_size - zero_segment_size_;
const uintptr_t hint_address = zero_segment_size_;
const uintptr_t allocated_base =
AllocPages(hint_address, allocation_size, PageAllocationGranularity(),
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible),
PageTag::kPartitionAlloc);
if (allocated_base == hint_address) {
zero_segment_size_ += allocation_size;
} else if (allocated_base) {
FreePages(allocated_base, allocation_size);
}
}
#endif
void PartitionAddressSpace::InitConfigurablePool(uintptr_t pool_base,
size_t size) {
PA_CHECK(!IsConfigurablePoolInitialized());
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
if (IsThreadIsolatedPoolInitialized()) {
UnprotectThreadIsolatedGlobals();
}
#endif
PA_CHECK(pool_base);
PA_CHECK(size <= kConfigurablePoolMaxSize);
PA_CHECK(size >= kConfigurablePoolMinSize);
PA_CHECK(base::bits::HasSingleBit(size));
PA_CHECK(pool_base % size == 0);
setup_.configurable_pool_base_address_ = pool_base;
setup_.configurable_pool_base_mask_ = ~(size - 1);
AddressPoolManager::GetInstance().Add(
kConfigurablePoolHandle, setup_.configurable_pool_base_address_, size);
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
if (IsThreadIsolatedPoolInitialized()) {
WriteProtectThreadIsolatedGlobals(setup_.thread_isolation_);
}
#endif
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
if (metadata_region_start_ != kUninitializedPoolBaseAddress) {
offsets_to_metadata_[kConfigurablePoolHandle] =
metadata_region_start_ - ConfigurablePoolBase() +
MetadataInnerOffset(kConfigurablePoolHandle);
} else {
offsets_to_metadata_[kConfigurablePoolHandle] = SystemPageSize();
}
#endif }
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
void PartitionAddressSpace::InitThreadIsolatedPool(
ThreadIsolationOption thread_isolation) {
if (IsThreadIsolatedPoolInitialized()) {
PA_CHECK(setup_.thread_isolation_ == thread_isolation);
return;
}
size_t pool_size = ThreadIsolatedPoolSize();
setup_.thread_isolated_pool_base_address_ =
AllocPages(pool_size, pool_size,
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible),
PageTag::kPartitionAlloc);
if (!setup_.thread_isolated_pool_base_address_) {
HandlePoolAllocFailure();
}
PA_DCHECK(!(setup_.thread_isolated_pool_base_address_ & (pool_size - 1)));
setup_.thread_isolation_ = thread_isolation;
AddressPoolManager::GetInstance().Add(
kThreadIsolatedPoolHandle, setup_.thread_isolated_pool_base_address_,
pool_size);
PA_DCHECK(
!IsInThreadIsolatedPool(setup_.thread_isolated_pool_base_address_ - 1));
PA_DCHECK(IsInThreadIsolatedPool(setup_.thread_isolated_pool_base_address_));
PA_DCHECK(IsInThreadIsolatedPool(setup_.thread_isolated_pool_base_address_ +
pool_size - 1));
PA_DCHECK(!IsInThreadIsolatedPool(setup_.thread_isolated_pool_base_address_ +
pool_size));
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
if (metadata_region_start_ != kUninitializedPoolBaseAddress) {
offsets_to_metadata_[kThreadIsolatedPoolHandle] =
metadata_region_start_ - setup_.thread_isolated_pool_base_address_ +
MetadataInnerOffset(kThreadIsolatedPoolHandle);
} else {
offsets_to_metadata_[kThreadIsolatedPoolHandle] = SystemPageSize();
}
#endif }
#endif
void PartitionAddressSpace::UninitForTesting() {
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
UninitThreadIsolatedPoolForTesting(); #endif
FreePages(setup_.regular_pool_base_address_, 2 * CorePoolSize());
setup_.regular_pool_base_address_ = kUninitializedPoolBaseAddress;
setup_.brp_pool_base_address_ = kUninitializedPoolBaseAddress;
setup_.configurable_pool_base_address_ = kUninitializedPoolBaseAddress;
setup_.configurable_pool_base_mask_ = 0;
AddressPoolManager::GetInstance().ResetForTesting();
#if PA_BUILDFLAG(ENABLE_POINTER_COMPRESSION)
CompressedPointerBaseGlobal::ResetBaseForTesting();
#endif
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
FreePages(metadata_region_start_, MetadataRegionSize());
metadata_region_start_ = kUninitializedPoolBaseAddress;
#if PA_CONFIG(DYNAMICALLY_SELECT_POOL_SIZE)
metadata_region_size_ = 0;
#endif for (size_t i = 0; i < kMaxPoolHandle; ++i) {
offsets_to_metadata_[i] = SystemPageSize();
}
#endif }
void PartitionAddressSpace::UninitConfigurablePoolForTesting() {
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
if (IsThreadIsolatedPoolInitialized()) {
UnprotectThreadIsolatedGlobals();
}
#endif
AddressPoolManager::GetInstance().Remove(kConfigurablePoolHandle);
setup_.configurable_pool_base_address_ = kUninitializedPoolBaseAddress;
setup_.configurable_pool_base_mask_ = 0;
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
if (IsThreadIsolatedPoolInitialized()) {
WriteProtectThreadIsolatedGlobals(setup_.thread_isolation_);
}
#endif
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
offsets_to_metadata_[kConfigurablePoolHandle] = SystemPageSize();
#endif }
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
void PartitionAddressSpace::UninitThreadIsolatedPoolForTesting() {
if (IsThreadIsolatedPoolInitialized()) {
UnprotectThreadIsolatedGlobals();
#if PA_BUILDFLAG(DCHECKS_ARE_ON)
ThreadIsolationSettings::settings.enabled = false;
#endif
FreePages(setup_.thread_isolated_pool_base_address_,
ThreadIsolatedPoolSize());
AddressPoolManager::GetInstance().Remove(kThreadIsolatedPoolHandle);
setup_.thread_isolated_pool_base_address_ = kUninitializedPoolBaseAddress;
setup_.thread_isolation_.enabled = false;
}
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
offsets_to_metadata_[kThreadIsolatedPoolHandle] = SystemPageSize();
#endif }
#endif
#if PA_CONFIG(MOVE_METADATA_OUT_OF_GIGACAGE)
void PartitionAddressSpace::InitMetadataRegionAndOffsets() {
if (metadata_region_start_ != kUninitializedPoolBaseAddress) {
return;
}
#if PA_BUILDFLAG(ENABLE_MOVE_METADATA_OUT_OF_GIGACAGE_TRIAL)
if (ExternalMetadataTrialGroup::kUndefined ==
GetExternalMetadataTrialGroup()) {
if (SelectExternalMetadataTrialGroup() !=
ExternalMetadataTrialGroup::kEnabled) {
for (size_t i = 0; i < kMaxPoolHandle; ++i) {
offsets_to_metadata_[i] = SystemPageSize();
}
return;
}
}
#endif
#if PA_CONFIG(DYNAMICALLY_SELECT_POOL_SIZE)
metadata_region_size_ = std::max(kConfigurablePoolMaxSize, CorePoolSize());
#endif
uintptr_t address =
AllocPages(MetadataRegionSize(), PageAllocationGranularity(),
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible),
PageTag::kPartitionAlloc);
if (!address) {
HandlePoolAllocFailure();
}
metadata_region_start_ = address;
PA_DCHECK(RegularPoolBase() != kUninitializedPoolBaseAddress);
PA_DCHECK(BRPPoolBase() != kUninitializedPoolBaseAddress);
offsets_to_metadata_[kRegularPoolHandle] =
address - RegularPoolBase() + MetadataInnerOffset(kRegularPoolHandle);
offsets_to_metadata_[kBRPPoolHandle] =
address - BRPPoolBase() + MetadataInnerOffset(kBRPPoolHandle);
offsets_to_metadata_[kConfigurablePoolHandle] = SystemPageSize();
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
offsets_to_metadata_[kThreadIsolatedPoolHandle] = SystemPageSize();
#endif }
#endif
#if defined(PARTITION_ALLOCATOR_CONSTANTS_POSIX_NONCONST_PAGE_SIZE)
PageCharacteristics page_characteristics;
#endif
#endif
}