#include "src/heap/code-range.h"
#include <algorithm>
#include <atomic>
#include <limits>
#include <utility>
#ifdef USING_OHOS_WEB
#include <random>
#endif
#include "src/base/bits.h"
#include "src/base/lazy-instance.h"
#include "src/base/once.h"
#include "src/codegen/constants-arch.h"
#include "src/common/globals.h"
#include "src/flags/flags.h"
#include "src/heap/heap-inl.h"
#include "src/utils/allocation.h"
#if defined(V8_OS_WIN64)
#include "src/diagnostics/unwinding-info-win64.h"
#endif
namespace v8 {
namespace internal {
namespace {
DEFINE_LAZY_LEAKY_OBJECT_GETTER(CodeRangeAddressHint, GetCodeRangeAddressHint)
void FunctionInStaticBinaryForAddressHint() {}
}
void RedZones::Initialize(base::BoundedPageAllocator* allocator) {
allocator_ = allocator;
}
bool RedZones::TryAdd(base::AddressRegion region) {
if (!allocator_) {
return false;
}
const base::AddressRegion allocator_region(allocator_->begin(),
allocator_->size());
const base::AddressRegion overlap = allocator_region.GetOverlap(region);
if (overlap.size() == 0) {
return false;
}
CHECK_EQ(0, overlap.size() % allocator_->AllocatePageSize());
red_zones_.emplace_back(overlap);
CHECK(allocator_->AllocatePagesAt(overlap.begin(), overlap.size(),
PageAllocator::kNoAccess));
return true;
}
bool RedZones::TryRemove(base::AddressRegion needle) {
if (!allocator_) {
return false;
}
const size_t allocate_page_size = allocator_->AllocatePageSize();
bool did_remove_region = false;
std::vector<base::AddressRegion> new_red_zones;
for (auto it = red_zones_.begin(); it != red_zones_.end();) {
const base::AddressRegion red_zone_region = *it;
const base::AddressRegion overlap = red_zone_region.GetOverlap(needle);
if (overlap.size() == 0) {
++it;
continue;
}
CHECK_EQ(0, overlap.size() % allocate_page_size);
allocator_->FreePages(reinterpret_cast<void*>(red_zone_region.begin()),
red_zone_region.size());
it = red_zones_.erase(it);
did_remove_region = true;
if (overlap.size() == red_zone_region.size()) {
continue;
}
const auto add_new_red_zone = [this, &new_red_zones, allocate_page_size](
Address red_zone_begin,
size_t red_zone_size) {
CHECK_EQ(0, red_zone_size % allocate_page_size);
CHECK(allocator_->AllocatePagesAt(red_zone_begin, red_zone_size,
PageAllocator::kNoAccess));
new_red_zones.emplace_back(red_zone_begin, red_zone_size);
};
if (red_zone_region.begin() != overlap.begin()) {
CHECK_GT(overlap.begin(), red_zone_region.begin());
add_new_red_zone(red_zone_region.begin(),
overlap.begin() - red_zone_region.begin());
}
if (red_zone_region.end() != overlap.end()) {
CHECK_LT(overlap.end(), red_zone_region.end());
add_new_red_zone(overlap.end(), red_zone_region.end() - overlap.end());
}
}
red_zones_.insert(red_zones_.end(), new_red_zones.begin(),
new_red_zones.end());
return did_remove_region;
}
Address CodeRangeAddressHint::GetAddressHint(size_t code_range_size,
size_t allocate_page_size) {
base::MutexGuard guard(&mutex_);
Address result = 0;
auto it = recently_freed_.find(code_range_size);
if (it == recently_freed_.end() || it->second.empty()) {
return RoundUp(FUNCTION_ADDR(&FunctionInStaticBinaryForAddressHint),
allocate_page_size);
}
result = it->second.back();
CHECK(IsAligned(result, allocate_page_size));
it->second.pop_back();
return result;
}
void CodeRangeAddressHint::NotifyFreedCodeRange(Address code_range_start,
size_t code_range_size) {
base::MutexGuard guard(&mutex_);
recently_freed_[code_range_size].push_back(code_range_start);
}
CodeRange::~CodeRange() { Free(); }
size_t CodeRange::GetWritableReservedAreaSize() {
return kReservedCodeRangePages * MemoryAllocator::GetCommitPageSize();
}
#define TRACE(...) \
if (v8_flags.trace_code_range_allocation) PrintF(__VA_ARGS__)
bool CodeRange::InitReservation(v8::PageAllocator* page_allocator,
size_t requested, bool immutable) {
DCHECK_NE(requested, 0);
if (V8_EXTERNAL_CODE_SPACE_BOOL) {
page_allocator = GetPlatformPageAllocator();
}
if (requested <= kMinimumCodeRangeSize) {
requested = kMinimumCodeRangeSize;
}
const size_t kPageSize = MutablePageMetadata::kPageSize;
const size_t allocate_page_size = page_allocator->AllocatePageSize();
CHECK(IsAligned(kPageSize, allocate_page_size));
DCHECK_IMPLIES(kPlatformRequiresCodeRange,
requested <= kMaximalCodeRangeSize);
VirtualMemoryCage::ReservationParams params;
params.page_allocator = page_allocator;
params.reservation_size = requested;
params.base_alignment =
VirtualMemoryCage::ReservationParams::kAnyBaseAlignment;
params.page_size = kPageSize;
if (v8_flags.jitless) {
params.permissions = PageAllocator::Permission::kNoAccess;
params.page_initialization_mode =
base::PageInitializationMode::kAllocatedPagesCanBeUninitialized;
params.page_freeing_mode = base::PageFreeingMode::kMakeInaccessible;
} else {
params.permissions = PageAllocator::Permission::kNoAccessWillJitLater;
params.page_initialization_mode =
base::PageInitializationMode::kRecommitOnly;
params.page_freeing_mode = base::PageFreeingMode::kDiscard;
}
#if defined(V8_TARGET_OS_IOS) || defined(V8_TARGET_OS_CHROMEOS) || \
defined(V8_HAS_JIT_FORT_PROTECT)
params.requested_start_hint = kNullAddress;
if (!VirtualMemoryCage::InitReservation(params)) return false;
#else
constexpr size_t kRadiusInMB =
kMaxPCRelativeCodeRangeInMB > 1024 ? kMaxPCRelativeCodeRangeInMB : 4096;
auto preferred_region = GetPreferredRegion(kRadiusInMB, kPageSize);
TRACE("=== Preferred region: [%p, %p)\n",
reinterpret_cast<void*>(preferred_region.begin()),
reinterpret_cast<void*>(preferred_region.end()));
const bool kShouldTryHarder = V8_EXTERNAL_CODE_SPACE_BOOL &&
COMPRESS_POINTERS_IN_SHARED_CAGE_BOOL &&
v8_flags.better_code_range_allocation;
if (kShouldTryHarder) {
VirtualMemoryCage candidate_cage;
#ifdef USING_OHOS_WEB
const int kAllocationTries = 128;
Address preferred_begin = RoundDown(preferred_region.begin(), kPageSize);
Address preferred_end =
RoundDown(preferred_region.end() - requested, kPageSize);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<size_t> dist(preferred_begin, preferred_end);
for (int i = 0; i < kAllocationTries; i++) {
params.requested_start_hint = RoundDown(dist(gen), kPageSize);
TRACE("=== Attempt #%d, hint=%p\n", i,
reinterpret_cast<void*>(params.requested_start_hint));
if (candidate_cage.InitReservation(params)) {
TRACE("=== Attempt #%d (%p): [%p, %p)\n", i,
reinterpret_cast<void*>(params.requested_start_hint),
reinterpret_cast<void*>(candidate_cage.region().begin()),
reinterpret_cast<void*>(candidate_cage.region().end()));
if (preferred_region.contains(candidate_cage.region())) break;
candidate_cage.Free();
}
}
#else
const int kAllocationTries = 16;
params.requested_start_hint =
RoundDown(preferred_region.end() - requested, kPageSize);
Address step =
RoundDown(preferred_region.size() / kAllocationTries, kPageSize);
for (int i = 0; i < kAllocationTries; i++) {
TRACE("=== Attempt #%d, hint=%p\n", i,
reinterpret_cast<void*>(params.requested_start_hint));
if (candidate_cage.InitReservation(params)) {
TRACE("=== Attempt #%d (%p): [%p, %p)\n", i,
reinterpret_cast<void*>(params.requested_start_hint),
reinterpret_cast<void*>(candidate_cage.region().begin()),
reinterpret_cast<void*>(candidate_cage.region().end()));
if (preferred_region.contains(candidate_cage.region())) break;
candidate_cage.Free();
}
if (step == 0) break;
params.requested_start_hint -= step;
}
#endif
if (candidate_cage.IsReserved()) {
*static_cast<VirtualMemoryCage*>(this) = std::move(candidate_cage);
}
}
if (!IsReserved()) {
Address the_hint = GetCodeRangeAddressHint()->GetAddressHint(
requested, allocate_page_size);
params.requested_start_hint = the_hint;
if (!VirtualMemoryCage::InitReservation(params)) {
params.requested_start_hint = kNullAddress;
if (!VirtualMemoryCage::InitReservation(params)) return false;
}
TRACE("=== Fallback attempt, hint=%p: [%p, %p)\n",
reinterpret_cast<void*>(params.requested_start_hint),
reinterpret_cast<void*>(region().begin()),
reinterpret_cast<void*>(region().end()));
}
if (v8_flags.abort_on_far_code_range &&
!preferred_region.contains(region())) {
FATAL("Failed to allocate code range close to the .text section");
}
#endif
#ifdef V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
SandboxHardwareSupport::RegisterOutOfSandboxMemory(
base(), size(), PagePermissions::kNoAccess);
#endif
const size_t required_writable_area_size = GetWritableReservedAreaSize();
size_t excluded_allocatable_area_size = 0;
if (required_writable_area_size > 0) {
CHECK_LE(required_writable_area_size, kPageSize);
const Address non_allocatable_size = page_allocator_->begin() - base();
TRACE("=== non-allocatable region: [%p, %p)\n",
reinterpret_cast<void*>(base()),
reinterpret_cast<void*>(base() + non_allocatable_size));
if (non_allocatable_size < required_writable_area_size) {
TRACE("=== Exclude the first page from allocatable area\n");
excluded_allocatable_area_size = kPageSize;
CHECK(page_allocator_->AllocatePagesAt(page_allocator_->begin(),
excluded_allocatable_area_size,
PageAllocator::kNoAccess));
}
if (!reservation()->SetPermissions(base(), required_writable_area_size,
PageAllocator::kReadWrite)) {
return false;
}
#if defined(V8_OS_WIN64)
if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange()) {
win64_unwindinfo::RegisterNonABICompliantCodeRange(
reinterpret_cast<void*>(base()), size());
}
#endif
}
#if CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
red_zones_.Initialize(page_allocator_.get());
CHECK_GE(size(), kContiguousReadOnlyReservationSize);
CHECK_GE(kPtrComprCageBaseAlignment - kContiguousReadOnlyReservationSize,
size());
CHECK_EQ(0, excluded_allocatable_area_size % allocate_page_size);
CHECK_EQ(0, kContiguousReadOnlyReservationSize % allocate_page_size);
const Address allocatable_begin =
page_allocator_->begin() + excluded_allocatable_area_size;
const Address allocatable_end =
page_allocator_->begin() + page_allocator_->size();
const Address window_begin =
RoundDown<kPtrComprCageBaseAlignment>(allocatable_begin);
const Address window_end =
RoundUp<kPtrComprCageBaseAlignment>(allocatable_end);
int number_of_red_zones = 0;
for (Address current = window_begin; current <= window_end;
current += kPtrComprCageBaseAlignment) {
const Address red_zone_start = std::max(current, allocatable_begin);
const Address red_zone_end =
std::min(current + kContiguousReadOnlyReservationSize, allocatable_end);
if (red_zone_start < red_zone_end) {
number_of_red_zones++;
const size_t red_zone_size = red_zone_end - red_zone_start;
CHECK_LT(red_zone_size, size());
CHECK_GE(red_zone_start, base());
CHECK_LE(red_zone_end, base() + size());
CHECK(red_zones_.TryAdd(
base::AddressRegion(red_zone_start, red_zone_size)));
}
}
CHECK_LE(red_zones_.num_red_zones(), 1);
CHECK_LE(number_of_red_zones, 1);
#endif
#if !defined(V8_OS_WIN) && !defined(V8_OS_IOS)
if (params.page_initialization_mode ==
base::PageInitializationMode::kRecommitOnly) {
void* base = reinterpret_cast<void*>(page_allocator_->begin() +
excluded_allocatable_area_size);
size_t size = page_allocator_->size() - excluded_allocatable_area_size;
if (ThreadIsolation::Enabled()) {
if (!ThreadIsolation::MakeExecutable(reinterpret_cast<Address>(base),
size)) {
return false;
}
} else if (!params.page_allocator->SetPermissions(
base, size, PageAllocator::kReadWriteExecute)) {
return false;
}
if (immutable) {
#ifdef DEBUG
immutable_ = true;
#endif
#ifdef V8_ENABLE_MEMORY_SEALING
params.page_allocator->SealPages(base, size);
#endif
}
DiscardSealedMemoryScope discard_scope("Discard global code range.");
if (!params.page_allocator->DiscardSystemPages(base, size)) return false;
}
#endif
return true;
}
base::AddressRegion CodeRange::GetPreferredRegion(size_t radius_in_megabytes,
size_t allocate_page_size) {
#ifdef V8_TARGET_ARCH_64_BIT
Address embedded_blob_code_start =
reinterpret_cast<Address>(Isolate::CurrentEmbeddedBlobCode());
Address embedded_blob_code_end;
if (embedded_blob_code_start == kNullAddress) {
embedded_blob_code_start =
FUNCTION_ADDR(&FunctionInStaticBinaryForAddressHint);
embedded_blob_code_end = embedded_blob_code_start + 1;
} else {
embedded_blob_code_end =
embedded_blob_code_start + Isolate::CurrentEmbeddedBlobCodeSize();
}
constexpr size_t max_size = std::numeric_limits<size_t>::max();
size_t radius = radius_in_megabytes * MB;
Address region_start =
RoundUp(embedded_blob_code_end - radius, allocate_page_size);
if (region_start > embedded_blob_code_end) {
region_start = 0;
}
Address region_end =
RoundDown(embedded_blob_code_start + radius, allocate_page_size);
if (region_end < embedded_blob_code_start) {
region_end = RoundDown(max_size, allocate_page_size);
}
constexpr size_t k4GB = size_t{4} * GB;
Address four_gb_cage_start = RoundDown(embedded_blob_code_start, k4GB);
Address four_gb_cage_end = four_gb_cage_start + k4GB;
region_start = std::max(region_start, four_gb_cage_start);
region_end = std::min(region_end, four_gb_cage_end);
return base::AddressRegion(region_start, region_end - region_start);
#else
return {};
#endif
}
void CodeRange::Free() {
if (IsReserved()) {
#if defined(V8_OS_WIN64)
if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange()) {
win64_unwindinfo::UnregisterNonABICompliantCodeRange(
reinterpret_cast<void*>(base()));
}
#endif
GetCodeRangeAddressHint()->NotifyFreedCodeRange(
reservation()->region().begin(), reservation()->region().size());
VirtualMemoryCage::Free();
}
}
uint8_t* CodeRange::RemapEmbeddedBuiltins(Isolate* isolate,
const uint8_t* embedded_blob_code,
size_t embedded_blob_code_size) {
base::MutexGuard guard(&remap_embedded_builtins_mutex_);
const base::AddressRegion code_region(page_allocator()->begin(),
page_allocator()->size());
CHECK_NE(code_region.begin(), kNullAddress);
CHECK(!code_region.is_empty());
uint8_t* embedded_blob_code_copy =
embedded_blob_code_copy_.load(std::memory_order_acquire);
if (embedded_blob_code_copy) {
DCHECK(
code_region.contains(reinterpret_cast<Address>(embedded_blob_code_copy),
embedded_blob_code_size));
SLOW_DCHECK(memcmp(embedded_blob_code, embedded_blob_code_copy,
embedded_blob_code_size) == 0);
return embedded_blob_code_copy;
}
const size_t allocate_page_size = page_allocator()->AllocatePageSize();
const size_t allocate_code_size =
RoundUp(embedded_blob_code_size, allocate_page_size);
const size_t max_pc_relative_code_range = kMaxPCRelativeCodeRangeInMB * MB;
const size_t blob_offset =
std::min(max_pc_relative_code_range, code_region.size()) -
allocate_code_size;
const Address blob_begin = code_region.begin() + blob_offset;
red_zones_.TryRemove(base::AddressRegion(blob_begin, allocate_code_size));
embedded_blob_code_copy =
reinterpret_cast<uint8_t*>(page_allocator()->AllocatePages(
reinterpret_cast<void*>(blob_begin), allocate_code_size,
allocate_page_size, PageAllocator::kNoAccessWillJitLater));
if (!embedded_blob_code_copy) {
V8::FatalProcessOutOfMemory(
isolate, "Can't allocate space for re-embedded builtins");
}
CHECK_EQ(embedded_blob_code_copy, reinterpret_cast<void*>(blob_begin));
if (code_region.size() > max_pc_relative_code_range) {
const Address unreachable_start =
reinterpret_cast<Address>(embedded_blob_code_copy) +
max_pc_relative_code_range;
if (code_region.contains(unreachable_start)) {
const size_t unreachable_size = code_region.end() - unreachable_start;
red_zones_.TryRemove(
base::AddressRegion(unreachable_start, unreachable_size));
void* result = page_allocator()->AllocatePages(
reinterpret_cast<void*>(unreachable_start), unreachable_size,
allocate_page_size, PageAllocator::kNoAccess);
CHECK_EQ(reinterpret_cast<Address>(result), unreachable_start);
}
}
red_zones_.Reset();
const size_t commit_page_size = page_allocator()->CommitPageSize();
size_t code_size = RoundUp(embedded_blob_code_size, commit_page_size);
#ifndef V8_HAS_JIT_FORT_PROTECT
if constexpr (base::OS::IsRemapPageSupported()) {
if (IsAligned(reinterpret_cast<uintptr_t>(embedded_blob_code),
commit_page_size)) {
bool ok = base::OS::RemapPages(embedded_blob_code, code_size,
embedded_blob_code_copy,
base::OS::MemoryPermission::kReadExecute);
if (ok) {
embedded_blob_code_copy_.store(embedded_blob_code_copy,
std::memory_order_release);
return embedded_blob_code_copy;
}
}
}
#endif
if (V8_HEAP_USE_PTHREAD_JIT_WRITE_PROTECT ||
V8_HEAP_USE_BECORE_JIT_WRITE_PROTECT || ThreadIsolation::Enabled()) {
#if !defined(V8_TARGET_OS_IOS)
if (!page_allocator()->RecommitPages(embedded_blob_code_copy, code_size,
PageAllocator::kReadWriteExecute)) {
V8::FatalProcessOutOfMemory(isolate,
"Re-embedded builtins: recommit pages");
}
#endif
RwxMemoryWriteScope rwx_write_scope(
"Enable write access to copy the blob code into the code range");
memcpy(embedded_blob_code_copy, embedded_blob_code,
embedded_blob_code_size);
} else {
if (!page_allocator()->SetPermissions(embedded_blob_code_copy, code_size,
PageAllocator::kReadWrite)) {
V8::FatalProcessOutOfMemory(isolate,
"Re-embedded builtins: set permissions");
}
memcpy(embedded_blob_code_copy, embedded_blob_code,
embedded_blob_code_size);
if (!page_allocator()->SetPermissions(embedded_blob_code_copy, code_size,
PageAllocator::kReadExecute)) {
V8::FatalProcessOutOfMemory(isolate,
"Re-embedded builtins: set permissions");
}
}
embedded_blob_code_copy_.store(embedded_blob_code_copy,
std::memory_order_release);
return embedded_blob_code_copy;
}
}
}