/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/base/virtual-address-space.cc
499 строк
19 KB
Michaël Zasso
deps: update V8 to 14.6.202.33
24 апр 2026, 19:01
Не верифицирован
24 апр 2026, 19:01
f1e0b83
Код
Авторство
О чём код?
// Copyright 2021 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/base/virtual-address-space.h" #include <algorithm> #include <cstring> #include <optional> #include "include/v8-platform.h" #include "src/base/bits.h" #include "src/base/platform/platform.h" namespace v8 { namespace base { namespace { OS::MemoryPermission ToMemoryPermission(PagePermissions permissions) { switch (permissions) { case PagePermissions::kNoAccess: return OS::MemoryPermission::kNoAccess; case PagePermissions::kRead: return OS::MemoryPermission::kRead; case PagePermissions::kReadWrite: return OS::MemoryPermission::kReadWrite; case PagePermissions::kReadWriteExecute: return OS::MemoryPermission::kReadWriteExecute; case PagePermissions::kReadExecute: return OS::MemoryPermission::kReadExecute; default: // Other combinations are currently not supported. UNREACHABLE(); } } bool IsValidMappingName(const std::string& name) { return std::all_of(name.begin(), name.end(), [](char c) { return std::isalnum(static_cast<unsigned char>(c)) || c == '-'; }); } } // namespace VirtualAddressSpace::VirtualAddressSpace() : VirtualAddressSpaceBase(OS::CommitPageSize(), OS::AllocatePageSize(), kNullAddress, std::numeric_limits<uintptr_t>::max(), PagePermissions::kReadWriteExecute) { #if V8_OS_WIN // On Windows, this additional step is required to lookup the VirtualAlloc2 // and friends functions. OS::EnsureWin32MemoryAPILoaded(); #endif // V8_OS_WIN DCHECK(bits::IsPowerOfTwo(page_size())); DCHECK(bits::IsPowerOfTwo(allocation_granularity())); DCHECK_GE(allocation_granularity(), page_size()); DCHECK(IsAligned(allocation_granularity(), page_size())); } void VirtualAddressSpace::SetRandomSeed(int64_t seed) { OS::SetRandomMmapSeed(seed); } Address VirtualAddressSpace::RandomPageAddress() { return reinterpret_cast<Address>(OS::GetRandomMmapAddr()); } Address VirtualAddressSpace::AllocatePages(Address hint, size_t size, size_t alignment, PagePermissions permissions) { DCHECK(IsAligned(alignment, allocation_granularity())); DCHECK(IsAligned(hint, alignment)); DCHECK(IsAligned(size, allocation_granularity())); return reinterpret_cast<Address>( OS::Allocate(reinterpret_cast<void*>(hint), size, alignment, ToMemoryPermission(permissions))); } void VirtualAddressSpace::FreePages(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); OS::Free(reinterpret_cast<void*>(address), size); } bool VirtualAddressSpace::SetPagePermissions(Address address, size_t size, PagePermissions permissions) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); return OS::SetPermissions(reinterpret_cast<void*>(address), size, ToMemoryPermission(permissions)); } bool VirtualAddressSpace::AllocateGuardRegion(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); void* hint = reinterpret_cast<void*>(address); void* result = OS::Allocate(hint, size, allocation_granularity(), OS::MemoryPermission::kNoAccess); if (result && result != hint) { OS::Free(result, size); } return result == hint; } void VirtualAddressSpace::FreeGuardRegion(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); OS::Free(reinterpret_cast<void*>(address), size); } bool VirtualAddressSpace::CanAllocateSubspaces() { return OS::CanReserveAddressSpace(); } Address VirtualAddressSpace::AllocateSharedPages(Address hint, size_t size, PagePermissions permissions, SharedMemoryHandle handle, uint64_t offset) { DCHECK(IsAligned(hint, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); DCHECK(IsAligned(offset, allocation_granularity())); return reinterpret_cast<Address>( OS::AllocateShared(reinterpret_cast<void*>(hint), size, ToMemoryPermission(permissions), handle, offset)); } void VirtualAddressSpace::FreeSharedPages(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); OS::FreeShared(reinterpret_cast<void*>(address), size); } std::unique_ptr<v8::VirtualAddressSpace> VirtualAddressSpace::AllocateSubspace( Address hint, size_t size, size_t alignment, PagePermissions max_page_permissions, std::optional<MemoryProtectionKeyId> key, std::optional<SharedMemoryHandle> handle) { DCHECK(IsAligned(alignment, allocation_granularity())); DCHECK(IsAligned(hint, alignment)); DCHECK(IsAligned(size, allocation_granularity())); std::optional<AddressSpaceReservation> reservation = OS::CreateAddressSpaceReservation( reinterpret_cast<void*>(hint), size, alignment, ToMemoryPermission(max_page_permissions), handle); if (!reservation.has_value()) return std::unique_ptr<v8::VirtualAddressSpace>(); return std::unique_ptr<v8::VirtualAddressSpace>(new VirtualAddressSubspace( *reservation, this, max_page_permissions, key)); } std::optional<VirtualAddressSpace::MemoryProtectionKeyId> VirtualAddressSpace::ActiveMemoryProtectionKey() { return std::nullopt; } bool VirtualAddressSpace::RecommitPages(Address address, size_t size, PagePermissions permissions) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); return OS::RecommitPages(reinterpret_cast<void*>(address), size, ToMemoryPermission(permissions)); } bool VirtualAddressSpace::DiscardSystemPages(Address address, size_t size) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); return OS::DiscardSystemPages(reinterpret_cast<void*>(address), size); } bool VirtualAddressSpace::DecommitPages(Address address, size_t size) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); return OS::DecommitPages(reinterpret_cast<void*>(address), size); } void VirtualAddressSpace::FreeSubspace(VirtualAddressSubspace* subspace) { OS::FreeAddressSpaceReservation(subspace->reservation_); } VirtualAddressSubspace::VirtualAddressSubspace( AddressSpaceReservation reservation, VirtualAddressSpaceBase* parent_space, PagePermissions max_page_permissions, std::optional<MemoryProtectionKeyId> key) : VirtualAddressSpaceBase(parent_space->page_size(), parent_space->allocation_granularity(), reinterpret_cast<Address>(reservation.base()), reservation.size(), max_page_permissions), reservation_(reservation), region_allocator_(reinterpret_cast<Address>(reservation.base()), reservation.size(), parent_space->allocation_granularity()), parent_space_(parent_space), pkey_(key) { #if V8_OS_WIN // On Windows, the address space reservation needs to be split and merged at // the OS level as well. region_allocator_.set_on_split_callback([this](Address start, size_t size) { DCHECK(IsAligned(start, allocation_granularity())); CHECK(reservation_.SplitPlaceholder(reinterpret_cast<void*>(start), size)); }); region_allocator_.set_on_merge_callback([this](Address start, size_t size) { DCHECK(IsAligned(start, allocation_granularity())); CHECK(reservation_.MergePlaceholders(reinterpret_cast<void*>(start), size)); }); #endif // V8_OS_WIN if (pkey_) { #if V8_HAS_PKU_SUPPORT // Here we assume that the initial page permissions are kNoAccess, which is // currently always the case. CHECK(base::MemoryProtectionKey::HasMemoryProtectionKeyAPIs()); base::MemoryProtectionKey::SetPermissionsAndKey( {base(), size()}, PagePermissions::kNoAccess, *pkey_); #else UNREACHABLE(); #endif // V8_HAS_PKU_SUPPORT } } VirtualAddressSubspace::~VirtualAddressSubspace() { // TODO(chromium:1218005) here or in the RegionAllocator destructor we should // assert that all allocations have been freed. Otherwise we may end up // leaking memory on Windows because VirtualFree(subspace_base, 0) will then // only free the first allocation in the subspace, not the entire subspace. parent_space_->FreeSubspace(this); } void VirtualAddressSubspace::SetRandomSeed(int64_t seed) { MutexGuard guard(&mutex_); rng_.SetSeed(seed); } Address VirtualAddressSubspace::RandomPageAddress() { MutexGuard guard(&mutex_); // Note: the random numbers generated here aren't uniformly distributed if the // size isn't a power of two. Address addr = base() + (static_cast<uint64_t>(rng_.NextInt64()) % size()); return RoundDown(addr, allocation_granularity()); } Address VirtualAddressSubspace::AllocatePages(Address hint, size_t size, size_t alignment, PagePermissions permissions) { DCHECK(IsAligned(alignment, allocation_granularity())); DCHECK(IsAligned(hint, alignment)); DCHECK(IsAligned(size, allocation_granularity())); DCHECK(IsSubset(permissions, max_page_permissions())); MutexGuard guard(&mutex_); Address address = region_allocator_.AllocateRegion(hint, size, alignment); if (address == RegionAllocator::kAllocationFailure) return kNullAddress; if (!reservation_.Allocate(reinterpret_cast<void*>(address), size, ToMemoryPermission(permissions))) { // This most likely means that we ran out of memory. CHECK_EQ(size, region_allocator_.FreeRegion(address)); return kNullAddress; } return address; } void VirtualAddressSubspace::FreePages(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); MutexGuard guard(&mutex_); // The order here is important: on Windows, the allocation first has to be // freed to a placeholder before the placeholder can be merged (during the // merge_callback) with any surrounding placeholder mappings. if (!reservation_.Free(reinterpret_cast<void*>(address), size)) { // This can happen due to an out-of-memory condition, such as running out // of available VMAs for the process. FatalOOM(OOMType::kProcess, "VirtualAddressSubspace::FreePages"); } CHECK_EQ(size, region_allocator_.FreeRegion(address)); if (!name_.empty()) { // Freeing pages (via MAP_FIXED) resets the name of the mapping. // So we need to set it again here. CHECK(OS::SetMemoryRegionName(reinterpret_cast<void*>(address), size, name_.c_str())); } #if V8_HAS_PKU_SUPPORT if (pkey_) { // Freeing pages in a subspace effectively means replacing them with fresh // pages. As such, we need to re-set the protection key on the new pages. // // TODO(saelo): consider making AddressSpaceReservation aware of memory // protection keys to move this logic closer to where the pages are // replaced to make this more clear. CHECK(base::MemoryProtectionKey::SetPermissionsAndKey( {address, size}, PagePermissions::kNoAccess, *pkey_)); } #endif // V8_HAS_PKU_SUPPORT } bool VirtualAddressSubspace::SetPagePermissions(Address address, size_t size, PagePermissions permissions) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); DCHECK(IsSubset(permissions, max_page_permissions())); return reservation_.SetPermissions(reinterpret_cast<void*>(address), size, ToMemoryPermission(permissions)); } bool VirtualAddressSubspace::AllocateGuardRegion(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); MutexGuard guard(&mutex_); // It is guaranteed that reserved address space is inaccessible, so we just // need to mark the region as in-use in the region allocator. return region_allocator_.AllocateRegionAt(address, size); } void VirtualAddressSubspace::FreeGuardRegion(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); MutexGuard guard(&mutex_); CHECK_EQ(size, region_allocator_.FreeRegion(address)); } Address VirtualAddressSubspace::AllocateSharedPages(Address hint, size_t size, PagePermissions permissions, SharedMemoryHandle handle, uint64_t offset) { DCHECK(IsAligned(hint, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); DCHECK(IsAligned(offset, allocation_granularity())); MutexGuard guard(&mutex_); Address address = region_allocator_.AllocateRegion(hint, size, allocation_granularity()); if (address == RegionAllocator::kAllocationFailure) return kNullAddress; if (!reservation_.AllocateShared(reinterpret_cast<void*>(address), size, ToMemoryPermission(permissions), handle, offset)) { CHECK_EQ(size, region_allocator_.FreeRegion(address)); return kNullAddress; } return address; } void VirtualAddressSubspace::FreeSharedPages(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); MutexGuard guard(&mutex_); // The order here is important: on Windows, the allocation first has to be // freed to a placeholder before the placeholder can be merged (during the // merge_callback) with any surrounding placeholder mappings. CHECK(reservation_.FreeShared(reinterpret_cast<void*>(address), size)); CHECK_EQ(size, region_allocator_.FreeRegion(address)); if (!name_.empty()) { // Freeing pages (via MAP_FIXED) resets the name of the mapping. // So we need to set it again here. CHECK(OS::SetMemoryRegionName(reinterpret_cast<void*>(address), size, name_.c_str())); } } std::unique_ptr<v8::VirtualAddressSpace> VirtualAddressSubspace::AllocateSubspace( Address hint, size_t size, size_t alignment, PagePermissions max_page_permissions, std::optional<MemoryProtectionKeyId> key, std::optional<SharedMemoryHandle> handle) { // File backed mapping isn't supported for subspaces. DCHECK(!handle.has_value()); #if V8_HAS_PKU_SUPPORT // We don't allow subspaces with different keys as that could be unexpected. // If we ever want to support this, we should probably require specifying // some flag like kSubspacesMayUseDifferentMemoryProtectionKey when creating // the initial subspace and checking that flag here. CHECK_IMPLIES(pkey_, pkey_ == key); #endif // V8_HAS_PKU_SUPPORT DCHECK(IsAligned(alignment, allocation_granularity())); DCHECK(IsAligned(hint, alignment)); DCHECK(IsAligned(size, allocation_granularity())); DCHECK(IsSubset(max_page_permissions, this->max_page_permissions())); MutexGuard guard(&mutex_); Address address = region_allocator_.AllocateRegion(hint, size, alignment); if (address == RegionAllocator::kAllocationFailure) { return std::unique_ptr<v8::VirtualAddressSpace>(); } std::optional<AddressSpaceReservation> reservation = reservation_.CreateSubReservation( reinterpret_cast<void*>(address), size, ToMemoryPermission(max_page_permissions)); if (!reservation.has_value()) { CHECK_EQ(size, region_allocator_.FreeRegion(address)); return nullptr; } return std::unique_ptr<v8::VirtualAddressSpace>(new VirtualAddressSubspace( *reservation, this, max_page_permissions, key)); } std::optional<VirtualAddressSpace::MemoryProtectionKeyId> VirtualAddressSubspace::ActiveMemoryProtectionKey() { return pkey_; } bool VirtualAddressSubspace::RecommitPages(Address address, size_t size, PagePermissions permissions) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); DCHECK(IsSubset(permissions, max_page_permissions())); return reservation_.RecommitPages(reinterpret_cast<void*>(address), size, ToMemoryPermission(permissions)); } bool VirtualAddressSubspace::DiscardSystemPages(Address address, size_t size) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); return reservation_.DiscardSystemPages(reinterpret_cast<void*>(address), size); } bool VirtualAddressSubspace::DecommitPages(Address address, size_t size) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); bool success = reservation_.DecommitPages(reinterpret_cast<void*>(address), size); if (success && !name_.empty()) { // Decommitting pages (via MAP_FIXED) resets the name of the mapping. // So we need to set it again here. CHECK(OS::SetMemoryRegionName(reinterpret_cast<void*>(address), size, name_.c_str())); } #if V8_HAS_PKU_SUPPORT if (success && pkey_) { // Decommitting pages in a subspace effectively means replacing them with // fresh pages. As such, we need to re-set the protection key on the new // pages. // // TODO(saelo): consider making AddressSpaceReservation aware of memory // protection keys to move this logic closer to where the pages are // replaced to make this more clear. CHECK(base::MemoryProtectionKey::SetPermissionsAndKey( {address, size}, PagePermissions::kNoAccess, *pkey_)); } #endif // V8_HAS_PKU_SUPPORT return success; } void VirtualAddressSubspace::FreeSubspace(VirtualAddressSubspace* subspace) { MutexGuard guard(&mutex_); AddressSpaceReservation reservation = subspace->reservation_; Address base = reinterpret_cast<Address>(reservation.base()); CHECK_EQ(reservation.size(), region_allocator_.FreeRegion(base)); CHECK(reservation_.FreeSubReservation(reservation)); } bool VirtualAddressSubspace::SetName(const std::string& name) { CHECK(IsValidMappingName(name)); if (reservation_.SetName(name.c_str())) { name_ = name; return true; } return false; } } // namespace base } // namespace v8