// Copyright 2022 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. #ifndef V8_COMMON_CODE_MEMORY_ACCESS_INL_H_ #define V8_COMMON_CODE_MEMORY_ACCESS_INL_H_ #include "src/common/code-memory-access.h" #include "src/flags/flags.h" #include "src/objects/instruction-stream-inl.h" #include "src/objects/instruction-stream.h" #include "src/objects/slots-inl.h" #include "src/objects/tagged.h" #if V8_HAS_PKU_JIT_WRITE_PROTECT #include "src/base/platform/memory-protection-key.h" #endif #if V8_HAS_PTHREAD_JIT_WRITE_PROTECT #include "src/base/platform/platform.h" #endif namespace v8 { namespace internal { RwxMemoryWriteScope::RwxMemoryWriteScope(const char* comment) { if (!v8_flags.jitless) { SetWritable(); } } RwxMemoryWriteScope::~RwxMemoryWriteScope() { if (!v8_flags.jitless) { SetExecutable(); } } WritableJitAllocation::~WritableJitAllocation() = default; WritableJitAllocation::WritableJitAllocation( Address addr, size_t size, ThreadIsolation::JitAllocationType type, JitAllocationSource source) : address_(addr), // The order of these is important. We need to create the write scope // before we lookup the Jit page, since the latter will take a mutex in // protected memory. write_scope_("WritableJitAllocation"), page_ref_(ThreadIsolation::LookupJitPage(addr, size)), allocation_(source == JitAllocationSource::kRegister ? page_ref_->RegisterAllocation(addr, size, type) : page_ref_->LookupAllocation(addr, size, type)) {} WritableJitAllocation::WritableJitAllocation( Address addr, size_t size, ThreadIsolation::JitAllocationType type) : address_(addr), allocation_(size, type) {} // static WritableJitAllocation WritableJitAllocation::ForNonExecutableMemory( Address addr, size_t size, ThreadIsolation::JitAllocationType type) { return WritableJitAllocation(addr, size, type); } // static WritableJitAllocation WritableJitAllocation::ForInstructionStream( Tagged istream) { return WritableJitAllocation( istream->address(), istream->Size(), ThreadIsolation::JitAllocationType::kInstructionStream, JitAllocationSource::kLookup); } WritableJumpTablePair::WritableJumpTablePair(Address jump_table_address, size_t jump_table_size, Address far_jump_table_address, size_t far_jump_table_size) : write_scope_("WritableJumpTablePair"), // Always split the pages since we are not guaranteed that the jump table // and far jump table are on the same JitPage. jump_table_pages_(ThreadIsolation::SplitJitPages( far_jump_table_address, far_jump_table_size, jump_table_address, jump_table_size)), jump_table_(jump_table_pages_.second.LookupAllocation( jump_table_address, jump_table_size, ThreadIsolation::JitAllocationType::kWasmJumpTable)), far_jump_table_(jump_table_pages_.first.LookupAllocation( far_jump_table_address, far_jump_table_size, ThreadIsolation::JitAllocationType::kWasmFarJumpTable)) {} template void WritableJitAllocation::WriteHeaderSlot(T value) { // This assert is no strict requirement, it just guards against // non-implemented functionality. static_assert(!is_taggable_v); if constexpr (offset == HeapObject::kMapOffset) { TaggedField::Relaxed_Store_Map_Word( HeapObject::FromAddress(address_), value); } else { WriteMaybeUnalignedValue(address_ + offset, value); } } template void WritableJitAllocation::WriteHeaderSlot(Tagged value, ReleaseStoreTag) { // These asserts are no strict requirements, they just guard against // non-implemented functionality. static_assert(offset != HeapObject::kMapOffset); TaggedField::Release_Store(HeapObject::FromAddress(address_), value); } template void WritableJitAllocation::WriteHeaderSlot(Tagged value, RelaxedStoreTag) { if constexpr (offset == HeapObject::kMapOffset) { TaggedField::Relaxed_Store_Map_Word( HeapObject::FromAddress(address_), value); } else { TaggedField::Relaxed_Store(HeapObject::FromAddress(address_), value); } } template void WritableJitAllocation::WriteProtectedPointerHeaderSlot(Tagged value, RelaxedStoreTag) { static_assert(offset != HeapObject::kMapOffset); TaggedField::Relaxed_Store( HeapObject::FromAddress(address_), value); } template V8_INLINE void WritableJitAllocation::WriteHeaderSlot(Address address, T value, RelaxedStoreTag tag) { CHECK_EQ(allocation_.Type(), ThreadIsolation::JitAllocationType::kInstructionStream); size_t offset = address - address_; Tagged tagged(value); switch (offset) { case InstructionStream::kCodeOffset: WriteProtectedPointerHeaderSlot(tagged, tag); break; case InstructionStream::kRelocationInfoOffset: WriteHeaderSlot(tagged, tag); break; default: UNREACHABLE(); } } void WritableJitAllocation::CopyCode(size_t dst_offset, const uint8_t* src, size_t num_bytes) { CopyBytes(reinterpret_cast(address_ + dst_offset), src, num_bytes); } void WritableJitAllocation::CopyData(size_t dst_offset, const uint8_t* src, size_t num_bytes) { CopyBytes(reinterpret_cast(address_ + dst_offset), src, num_bytes); } void WritableJitAllocation::ClearBytes(size_t offset, size_t len) { memset(reinterpret_cast(address_ + offset), 0, len); } WritableJitPage::~WritableJitPage() = default; WritableJitPage::WritableJitPage(Address addr, size_t size) : write_scope_("WritableJitPage"), page_ref_(ThreadIsolation::LookupJitPage(addr, size)) {} WritableJitAllocation WritableJitPage::LookupAllocationContaining( Address addr) { auto pair = page_ref_.AllocationContaining(addr); return WritableJitAllocation(pair.first, pair.second.Size(), pair.second.Type()); } V8_INLINE WritableFreeSpace WritableJitPage::FreeRange(Address addr, size_t size) { page_ref_.UnregisterRange(addr, size); return WritableFreeSpace(addr, size, true); } WritableFreeSpace::~WritableFreeSpace() = default; // static V8_INLINE WritableFreeSpace WritableFreeSpace::ForNonExecutableMemory(base::Address addr, size_t size) { return WritableFreeSpace(addr, size, false); } V8_INLINE WritableFreeSpace::WritableFreeSpace(base::Address addr, size_t size, bool executable) : address_(addr), size_(static_cast(size)), executable_(executable) {} template void WritableFreeSpace::WriteHeaderSlot(Tagged value, RelaxedStoreTag) const { Tagged object = HeapObject::FromAddress(address_); // TODO(v8:13355): add validation before the write. if constexpr (offset == HeapObject::kMapOffset) { TaggedField::Relaxed_Store_Map_Word(object, value); } else { TaggedField::Relaxed_Store(object, value); } } template void WritableFreeSpace::ClearTagged(size_t count) const { base::Address start = address_ + offset; // TODO(v8:13355): add validation before the write. MemsetTagged(ObjectSlot(start), Tagged(kClearedFreeMemoryValue), count); } #if V8_HAS_PTHREAD_JIT_WRITE_PROTECT // static bool RwxMemoryWriteScope::IsSupported() { return true; } // static void RwxMemoryWriteScope::SetWritable() { if (code_space_write_nesting_level_ == 0) { base::SetJitWriteProtected(0); } code_space_write_nesting_level_++; } // static void RwxMemoryWriteScope::SetExecutable() { code_space_write_nesting_level_--; if (code_space_write_nesting_level_ == 0) { base::SetJitWriteProtected(1); } } #elif V8_HAS_PKU_JIT_WRITE_PROTECT // static bool RwxMemoryWriteScope::IsSupported() { static_assert(base::MemoryProtectionKey::kNoMemoryProtectionKey == -1); DCHECK(ThreadIsolation::initialized()); // TODO(sroettger): can we check this at initialization time instead? The // tests won't be able to run with/without pkey support anymore in the same // process. return v8_flags.memory_protection_keys && ThreadIsolation::pkey() >= 0; } // static void RwxMemoryWriteScope::SetWritable() { DCHECK(ThreadIsolation::initialized()); if (!IsSupported()) return; if (code_space_write_nesting_level_ == 0) { DCHECK_NE( base::MemoryProtectionKey::GetKeyPermission(ThreadIsolation::pkey()), base::MemoryProtectionKey::kNoRestrictions); base::MemoryProtectionKey::SetPermissionsForKey( ThreadIsolation::pkey(), base::MemoryProtectionKey::kNoRestrictions); } code_space_write_nesting_level_++; } // static void RwxMemoryWriteScope::SetExecutable() { DCHECK(ThreadIsolation::initialized()); if (!IsSupported()) return; code_space_write_nesting_level_--; if (code_space_write_nesting_level_ == 0) { DCHECK_EQ( base::MemoryProtectionKey::GetKeyPermission(ThreadIsolation::pkey()), base::MemoryProtectionKey::kNoRestrictions); base::MemoryProtectionKey::SetPermissionsForKey( ThreadIsolation::pkey(), base::MemoryProtectionKey::kDisableWrite); } } #else // !V8_HAS_PTHREAD_JIT_WRITE_PROTECT && !V8_TRY_USE_PKU_JIT_WRITE_PROTECT // static bool RwxMemoryWriteScope::IsSupported() { return false; } // static void RwxMemoryWriteScope::SetWritable() {} // static void RwxMemoryWriteScope::SetExecutable() {} #endif // V8_HAS_PTHREAD_JIT_WRITE_PROTECT } // namespace internal } // namespace v8 #endif // V8_COMMON_CODE_MEMORY_ACCESS_INL_H_