// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/globals.h" #if defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_LINUX) || \ defined(DART_HOST_OS_MACOS) #include "vm/virtual_memory.h" #include #include #include #include #include #include #if defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_LINUX) #include #endif #if defined(DART_HOST_OS_MACOS) #include #include #endif #if defined(DART_ENABLE_RX_WORKAROUNDS) #include #include #include #include #include #include "platform/syslog.h" #include "vm/cpu.h" #endif #include "platform/assert.h" #include "platform/thread_sanitizer.h" #include "platform/utils.h" #include "vm/heap/pages.h" #include "vm/isolate.h" #include "vm/virtual_memory_compressed.h" // #define VIRTUAL_MEMORY_LOGGING 1 #if defined(VIRTUAL_MEMORY_LOGGING) #define LOG_INFO(msg, ...) OS::PrintErr(msg, ##__VA_ARGS__) #else #define LOG_INFO(msg, ...) #endif // defined(VIRTUAL_MEMORY_LOGGING) namespace dart { // standard MAP_FAILED causes "error: use of old-style cast" as it // defines MAP_FAILED as ((void *) -1) #undef MAP_FAILED #define MAP_FAILED reinterpret_cast(-1) DECLARE_FLAG(bool, write_protect_code); #if defined(DART_TARGET_OS_LINUX) DECLARE_FLAG(bool, generate_perf_events_symbols); DECLARE_FLAG(bool, generate_perf_jitdump); #endif uword VirtualMemory::page_size_ = 0; VirtualMemory* VirtualMemory::compressed_heap_ = nullptr; #if defined(DART_ENABLE_RX_WORKAROUNDS) bool VirtualMemory::should_dual_map_executable_pages_ = false; #endif // defined(DART_ENABLE_RX_WORKAROUNDS) static void* Map(void* addr, size_t length, int prot, int flags, int fd, off_t offset) { void* result = mmap(addr, length, prot, flags, fd, offset); int error = errno; LOG_INFO("mmap(%p, 0x%" Px ", %u, ...): %p\n", addr, length, prot, result); if ((result == MAP_FAILED) && (error != ENOMEM)) { const int kBufferSize = 1024; char error_buf[kBufferSize]; FATAL("mmap failed: %d (%s)", error, Utils::StrError(error, error_buf, kBufferSize)); } return result; } static void Unmap(uword start, uword end) { ASSERT(start <= end); uword size = end - start; if (size == 0) { return; } if (munmap(reinterpret_cast(start), size) != 0) { int error = errno; const int kBufferSize = 1024; char error_buf[kBufferSize]; FATAL("munmap failed: %d (%s)", error, Utils::StrError(error, error_buf, kBufferSize)); } } static void* GenericMapAligned(void* hint, int prot, intptr_t size, intptr_t alignment, intptr_t allocated_size, int map_flags) { #if defined(DART_HOST_OS_MACOS) && !defined(USING_THREAD_SANITIZER) // Allocate aligned memory in one step when possible. // vm_map doesn't support MAP_JIT. // tsan is missing an interceptor for mach_vm_map. if ((map_flags & MAP_JIT) == 0) { vm_address_t address = 0; vm_prot_t cur_prot = 0; if ((prot & PROT_READ) != 0) cur_prot |= VM_PROT_READ; if ((prot & PROT_WRITE) != 0) cur_prot |= VM_PROT_WRITE; if ((prot & PROT_EXEC) != 0) cur_prot |= VM_PROT_EXECUTE; vm_prot_t max_prot = VM_PROT_ALL; const kern_return_t result = vm_map(mach_task_self(), &address, size, /*mask=*/alignment - 1, VM_FLAGS_ANYWHERE, MEMORY_OBJECT_NULL, /*offset=*/0, /*copy=*/FALSE, cur_prot, max_prot, VM_INHERIT_DEFAULT); if (result != KERN_SUCCESS) { return nullptr; } return reinterpret_cast(address); } #endif void* address = Map(hint, allocated_size, prot, map_flags, -1, 0); if (address == MAP_FAILED) { return nullptr; } const uword base = reinterpret_cast(address); const uword aligned_base = Utils::RoundUp(base, alignment); Unmap(base, aligned_base); Unmap(aligned_base + size, base + allocated_size); return reinterpret_cast(aligned_base); } intptr_t VirtualMemory::CalculatePageSize() { const intptr_t page_size = getpagesize(); ASSERT(page_size != 0); ASSERT(Utils::IsPowerOfTwo(page_size)); return page_size; } #if defined(DART_ENABLE_RX_WORKAROUNDS) // The function NOTIFY_DEBUGGER_ABOUT_RX_PAGES is a hook point for the debugger. // // We expect that LLBD is configured to intercept calls to this function and // takes care of writing into all pages covered by [base, base+size) address // range. // // For example, you can define the following Python helper script: // // ```python // # rx_helper.py // import lldb // // def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): // """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" // base = frame.register["x0"].GetValueAsAddress() // page_len = frame.register["x1"].GetValueAsUnsigned() // # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the // # first page to see if handled it correctly. This makes diagnosing // # misconfiguration (e.g. missing breakpoint) easier. // data = bytearray(page_len) // data[0:8] = b'IHELPED!'; // error = lldb.SBError() // frame.GetThread().GetProcess().WriteMemory(base, data, error) // if not error.Success(): // print(f'Failed to write into {base}[+{page_len}]', error) // return // // def __lldb_init_module(debugger: lldb.SBDebugger, _): // target = debugger.GetDummyTarget() // # Caveat: must use BreakpointCreateByRegEx here and not // # BreakpointCreateByName. For some reasons callback function does not // # get carried over from dummy target for the later. // bp = target.bpCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") // bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) // bp.SetAutoContinue(True) // print("-- LLDB integration loaded --") // ``` // // Which is then imported into LLDB via `.lldbinit` script: // // ``` // # .lldbinit // command script import --relative-to-command-file rx_helper.py // ``` // // XCode allows configuring custom LLDB Init Files: see Product -> Scheme -> // Run -> Info -> LLDB Init File, you can use `$(SRCROOT)/...` to place LLDB // script inside project directory itself. // __attribute__((noinline)) __attribute__((visibility("default"))) extern "C" void NOTIFY_DEBUGGER_ABOUT_RX_PAGES(void* base, size_t size) { // Note: need this to prevent LLVM from optimizing it away even with // noinline. asm volatile("" ::"r"(base), "r"(size) : "memory"); } namespace { // Handler for EXC_BAD_ACCESS which resumes the thread at the caller of the // function (sets PC = LR and R0 = kExceptionalReturnValue). This exception // handler is used to check if we can successfully create executable code // dynamically: see |CheckIfRXWorks| below. // // Note: the handler is using Mach kernel APIs for exception handling instead // of POSIX signals because using Mach APIs allows to intercept EXC_BAD_ACCESS // before it stops the debugger. class ScopedExcBadAccessHandler { public: static constexpr int32_t kExceptionalReturnValue = 0xDEADDEAD; ScopedExcBadAccessHandler() { mach_port_options_t options; memset(&options, 0, sizeof(options)); options.flags = MPO_INSERT_SEND_RIGHT; mach_port_t exception_port = MACH_PORT_NULL; kern_return_t kr = mach_port_construct(mach_task_self(), &options, 0, &exception_port); RELEASE_ASSERT(kr == KERN_SUCCESS); dispatch_source_t source = source_ = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, exception_port, 0, DISPATCH_TARGET_QUEUE_DEFAULT); RELEASE_ASSERT(source); // Process exceptions: ProcessMachExceptionRaiseStateMessage decodes // the message and forwards it to IgnoreExceptionAndReturnToCaller. dispatch_source_set_event_handler(source, ^{ constexpr mach_msg_size_t kMaxMessageSize = 5 * KB; mach_msg_server_once(ProcessMachExceptionRaiseStateMessage, kMaxMessageSize, exception_port, MACH_MSG_TIMEOUT_NONE); }); // When this handler is no longer needed destroy the port. dispatch_source_set_cancel_handler(source, ^{ mach_port_deallocate(mach_task_self(), exception_port); // Note: don't capture this because it will be invalid by the time // cancelation handler is called. dispatch_release(source); }); dispatch_resume(source); old_mask_count_ = 1; // We expect at most one old handler. kern_return_t result = thread_swap_exception_ports( mach_thread_self(), EXC_MASK_BAD_ACCESS, exception_port, MACH_EXCEPTION_CODES | EXCEPTION_STATE, MACHINE_THREAD_STATE, &old_exception_mask_, &old_mask_count_, &old_handler_, &old_behavior_, &old_flavor_); RELEASE_ASSERT(result == KERN_SUCCESS); RELEASE_ASSERT(old_mask_count_ == 1); } ~ScopedExcBadAccessHandler() { kern_return_t result = thread_set_exception_ports(mach_thread_self(), old_exception_mask_, old_handler_, old_behavior_, old_flavor_); RELEASE_ASSERT(result == KERN_SUCCESS); dispatch_source_cancel(source_); } private: // This exception handler simply ignores the EXC_BAD_ACCESS and // makes the thread continue at the caller frame by setting PC to LR and // X0 to a special signal value. // // The signature of this handler matches |catch_exception_raise_state|. // // See https://developer.apple.com/documentation/kernel/1537255-catch_exception_raise_state static kern_return_t IgnoreExceptionAndReturnToCaller( mach_port_t exception_port, exception_type_t exception, const mach_exception_data_t code, mach_msg_type_number_t code_count, int* flavor, const thread_state_t old_state, mach_msg_type_number_t old_state_count, thread_state_t new_state, mach_msg_type_number_t* new_state_count) { // Copy old_state into new_state. memmove(new_state, old_state, sizeof(*old_state) * old_state_count); *new_state_count = old_state_count; // Update X0 and PC so that we can successfully resume execution. auto arm_new_state = reinterpret_cast(new_state); arm_new_state->ts_64.__x[0] = kExceptionalReturnValue; arm_new_state->ts_64.__pc = arm_new_state->ts_64.__lr; return KERN_SUCCESS; } // The code in |ProcessMachExceptionRaiseStateMessage| and corresponding // structure definitions are based on output of the mig // (Mach Interface Generator, see |man mig|) applied to mach/mach_exc.defs. // // Including mig output directly is undesirable because it relies on // linking to exception handling routines by name (e.g. it expects // special symbols like mach_catch_exception_raise_state, // mach_catch_exception_raise_state_identity, to be defined). This might // make Dart VM harder to embed - as some other part of the code base might // want to use mig generated exception handling code and already define // this symbols. // // Thus we rewrite that code dropping all irrelevant bits and making the // code more readable. // // Request message for mach_exception_raise_state has two variadic arrays // inside, so we split it into two chunks each ending with a corresponding // variadic array. #define TRAILING_ARRAY(Type, name, count, max_count) \ Type* name() { return reinterpret_cast(this + 1); } \ bool IsValid() const { return count <= max_count; } \ mach_msg_size_t Size() const { return sizeof(*this) + sizeof(Type) * count; } // A helper method for parsing a message which contains variadic arrays // inside. Such message is split into separate chunks each ending with // a trailing array. template static std::tuple ParseMessage(mach_msg_header_t* header) { uword current = reinterpret_cast(header); mach_msg_size_t remaining = header->msgh_size; const uword message_end = current + remaining; std::tuple result{ true, [¤t, &remaining]() -> Ts* { if (remaining >= sizeof(Ts)) { Ts* chunk = reinterpret_cast(current); const auto chunk_size = chunk->Size(); if (chunk->IsValid() && chunk_size <= remaining) { current += chunk_size; remaining -= chunk_size; return chunk; } } // Once an error is encountered shortcut the rest of the parsing // by setting number of remaining bytes to 0. remaining = 0; current = 0; return nullptr; }()...}; // If we did not fully parse the message - we have either failed or // we have unparsed bytes. Either case is an error. if (current != message_end) { return {false, static_cast(nullptr)...}; } return result; } #pragma pack(push, 4) static constexpr natural_t kMaxCodeCount = 2; static constexpr natural_t kMaxStateCount = 1296; struct RequestChunk0 { mach_msg_header_t Head; NDR_record_t NDR; exception_type_t exception; mach_msg_type_number_t code_count; // <= kMaxCodeCount TRAILING_ARRAY(int64_t, code, code_count, kMaxCodeCount); }; struct RequestChunk1 { int flavor; mach_msg_type_number_t old_state_count; // <= kMaxStateCount TRAILING_ARRAY(natural_t, old_state, old_state_count, kMaxStateCount); }; struct Reply { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; int flavor; mach_msg_type_number_t new_state_count; TRAILING_ARRAY(natural_t, new_state, new_state_count, kMaxStateCount); }; #pragma pack(pop) static boolean_t ReplyWithError(mach_msg_header_t* reply_header, kern_return_t code) { auto reply = reinterpret_cast(reply_header); reply->RetCode = code; reply->NDR = NDR_record; return FALSE; } static boolean_t ProcessMachExceptionRaiseStateMessage( mach_msg_header_t* request_header, mach_msg_header_t* reply_header) { reply_header->msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request_header->msgh_bits), 0); reply_header->msgh_remote_port = request_header->msgh_remote_port; // Minimal size: will update later if success. reply_header->msgh_size = static_cast(sizeof(mig_reply_error_t)); reply_header->msgh_local_port = MACH_PORT_NULL; reply_header->msgh_id = request_header->msgh_id + 100; reply_header->msgh_reserved = 0; if (request_header->msgh_id != 2406) { return ReplyWithError(reply_header, MIG_BAD_ID); } if (request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { return ReplyWithError(reply_header, MIG_BAD_ARGUMENTS); } auto [ok, req0, req1] = ParseMessage(request_header); if (!ok) { return ReplyWithError(reply_header, MIG_BAD_ARGUMENTS); } auto reply = reinterpret_cast(reply_header); reply->new_state_count = kMaxStateCount; reply->RetCode = IgnoreExceptionAndReturnToCaller( request_header->msgh_local_port, req0->exception, req0->code(), req0->code_count, &req1->flavor, req1->old_state(), req1->old_state_count, reply->new_state(), &reply->new_state_count); if (reply->RetCode != KERN_SUCCESS) { return ReplyWithError(reply_header, reply->RetCode); } reply->NDR = NDR_record; reply->flavor = req1->flavor; reply->Head.msgh_size = reply->Size(); return TRUE; } dispatch_source_t source_ = nullptr; // Old exception handler (e.g. one installed by the debugger or some // other library). natural_t old_mask_count_ = 0; exception_mask_t old_exception_mask_ = 0; mach_port_t old_handler_ = MACH_PORT_NULL; exception_behavior_t old_behavior_ = 0; thread_state_flavor_t old_flavor_ = 0; DISALLOW_COPY_AND_ASSIGN(ScopedExcBadAccessHandler); }; // Check if we can generate machine code dynamically by creating a small // function in memory and then trying to execute it. // // Returns true if that was successful. // // Note: we use Syslog::PrintErr below instead of OS::PrintErr to send // output to the same location where FATAL message would be reported to if any. bool CheckIfRXWorks() { // Try creating executable VirtualMemory. std::unique_ptr mem{ VirtualMemory::Allocate(VirtualMemory::PageSize(), /*is_executable=*/true, /*is_compressed=*/false, /*name=*/nullptr)}; if (mem == nullptr) { Syslog::PrintErr("Failed to map a test RX page"); return false; } // Freshly created virtual memory should have signs of debugger script // working. See the comment above for the example of an LLDB script. const bool debugger_script_loaded = memcmp(mem->address(), "IHELPED!", 8) == 0; // Flip memory to RW write a simple function that computes a 32-bit integer // square and then flip protection back to R/RX. mem->Protect(VirtualMemory::kReadWrite); constexpr uint32_t kSquareFunctionCode[] = { 0x1b007c00, // mul w0, w0, w0 0xd65f03c0 // ret }; memmove(mem->address(), kSquareFunctionCode, sizeof(kSquareFunctionCode)); VirtualMemory::WriteProtectCode(mem->address(), mem->size()); // Get executable entry point and check that write have succeeded. const uword entry_point = mem->start() + mem->OffsetToExecutableAlias(); if (memcmp(reinterpret_cast(entry_point), kSquareFunctionCode, sizeof(kSquareFunctionCode)) != 0) { Syslog::PrintErr("Failed to write executable code: code mismatch"); return false; } CPU::FlushICache(entry_point, sizeof(kSquareFunctionCode)); constexpr int32_t kInput = 11; constexpr int32_t kExpectedOutput = kInput * kInput; // Invoke square function and catch any potential EXC_BAD_ACCESS. int32_t result = 0; { ScopedExcBadAccessHandler exception_handler; auto square = reinterpret_cast(entry_point); result = square(kInput); } // Validate that the code we have generated produced expected result. if (result != kExpectedOutput) { Syslog::PrintErr( "Failed to execute code (error: %s, debugger assist: %s)\n", result == ScopedExcBadAccessHandler::kExceptionalReturnValue ? "EXC_BAD_ACCESS" : "unknown", debugger_script_loaded ? "ok" : "not detected"); return false; } return true; } } // namespace #endif void VirtualMemory::Init() { if (FLAG_old_gen_heap_size < 0 || FLAG_old_gen_heap_size > kMaxAddrSpaceMB) { OS::PrintErr( "warning: value specified for --old_gen_heap_size %d is larger than" " the physically addressable range, using 0(unlimited) instead.`\n", FLAG_old_gen_heap_size); FLAG_old_gen_heap_size = 0; } if (FLAG_new_gen_semi_max_size < 0 || FLAG_new_gen_semi_max_size > kMaxAddrSpaceMB) { OS::PrintErr( "warning: value specified for --new_gen_semi_max_size %d is larger" " than the physically addressable range, using %" Pd " instead.`\n", FLAG_new_gen_semi_max_size, kDefaultNewGenSemiMaxSize); FLAG_new_gen_semi_max_size = kDefaultNewGenSemiMaxSize; } page_size_ = CalculatePageSize(); #if defined(DART_ENABLE_RX_WORKAROUNDS) bool can_jit = true; if (IsAtLeastIOS26_0()) { should_dual_map_executable_pages_ = true; can_jit = CheckIfRXWorks(); } #if defined(DART_INCLUDE_SIMULATOR) FLAG_use_simulator = !can_jit; Syslog::PrintErr("Dart execution mode: %s\n", FLAG_use_simulator ? "simulator" : "JIT"); #else if (!can_jit) { FATAL( "Unable to JIT: failed to create executable machine code dynamically " "due to OS restrictions"); } #endif #endif #if defined(DART_COMPRESSED_POINTERS) ASSERT(compressed_heap_ == nullptr); compressed_heap_ = Reserve(kGuardRegionSize * 2 + kCompressedHeapSize, kCompressedHeapAlignment); if (compressed_heap_ == nullptr) { int error = errno; const int kBufferSize = 1024; char error_buf[kBufferSize]; FATAL("Failed to reserve region for compressed heap: %d (%s)", error, Utils::StrError(error, error_buf, kBufferSize)); } VirtualMemoryCompressedHeap::Init( reinterpret_cast(compressed_heap_->start() + kGuardRegionSize), kCompressedHeapSize); #endif // defined(DART_COMPRESSED_POINTERS) #if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID) FILE* fp = fopen("/proc/sys/vm/max_map_count", "r"); if (fp != nullptr) { size_t max_map_count = 0; int count = fscanf(fp, "%zu", &max_map_count); fclose(fp); if (count == 1) { size_t max_heap_pages = FLAG_old_gen_heap_size * MB / Page::kPageSize; if (max_map_count < max_heap_pages) { OS::PrintErr( "warning: vm.max_map_count (%zu) is not large enough to support " "--old_gen_heap_size=%d. Consider increasing it with `sysctl -w " "vm.max_map_count=%zu`\n", max_map_count, FLAG_old_gen_heap_size, max_heap_pages); } } } #endif } void VirtualMemory::Cleanup() { #if defined(DART_COMPRESSED_POINTERS) delete compressed_heap_; #endif // defined(DART_COMPRESSED_POINTERS) page_size_ = 0; #if defined(DART_COMPRESSED_POINTERS) compressed_heap_ = nullptr; VirtualMemoryCompressedHeap::Cleanup(); #endif // defined(DART_COMPRESSED_POINTERS) } VirtualMemory* VirtualMemory::AllocateAligned(intptr_t size, intptr_t alignment, bool is_executable, bool is_compressed, const char* name) { // When FLAG_write_protect_code is active, code memory (indicated by // is_executable = true) is allocated as non-executable and later // changed to executable via VirtualMemory::Protect. ASSERT(Utils::IsAligned(size, PageSize())); ASSERT(Utils::IsPowerOfTwo(alignment)); ASSERT(Utils::IsAligned(alignment, PageSize())); ASSERT(name != nullptr); // Ignore executable for gen_snapshot/simulator, but still let the heap // track code and data pages separately. if (!VirtualMemory::ExecutesGeneratedCode()) { is_executable = false; } #if defined(DART_COMPRESSED_POINTERS) if (is_compressed) { RELEASE_ASSERT(!is_executable); MemoryRegion region = VirtualMemoryCompressedHeap::Allocate(size, alignment); if (region.pointer() == nullptr) { return nullptr; } Commit(region.pointer(), region.size()); return new VirtualMemory(region, region); } #endif // defined(DART_COMPRESSED_POINTERS) const intptr_t allocated_size = size + alignment - PageSize(); #if defined(DART_ENABLE_RX_WORKAROUNDS) // We need to map the original page using RX for dual mapping to have // effect on iOS. const int prot = (is_executable && should_dual_map_executable_pages_) ? PROT_READ | PROT_EXEC : PROT_READ | PROT_WRITE; #else const int prot = PROT_READ | PROT_WRITE | ((is_executable && !FLAG_write_protect_code) ? PROT_EXEC : 0); #endif int map_flags = MAP_PRIVATE | MAP_ANONYMOUS; #if (defined(DART_HOST_OS_MACOS) && !defined(DART_HOST_OS_IOS)) if (is_executable && IsAtLeastMacOSX10_14() && !ShouldDualMapExecutablePages()) { map_flags |= MAP_JIT; } #endif // defined(DART_HOST_OS_MACOS) void* hint = nullptr; // Some 64-bit microarchitectures store only the low 32-bits of targets as // part of indirect branch prediction, predicting that the target's upper bits // will be same as the call instruction's address. This leads to misprediction // for indirect calls crossing a 4GB boundary. We ask mmap to place our // generated code near the VM binary to avoid this. if (is_executable) { hint = reinterpret_cast(&Dart_Initialize); } void* address = GenericMapAligned(hint, prot, size, alignment, allocated_size, map_flags); #if defined(DART_HOST_OS_LINUX) // On WSL 1 trying to allocate memory close to the binary by supplying a hint // fails with ENOMEM for unclear reason. Some reports suggest that this might // be related to the alignment of the hint but aligning it by 64Kb does not // make the issue go away in our experiments. Instead just retry without any // hint. if (address == nullptr && hint != nullptr && Utils::IsWindowsSubsystemForLinux()) { address = GenericMapAligned(nullptr, prot, size, alignment, allocated_size, map_flags); } #endif if (address == nullptr) { return nullptr; } #if defined(DART_ENABLE_RX_WORKAROUNDS) if (is_executable && should_dual_map_executable_pages_) { // |address| is mapped RX, create a corresponding RW alias through which // we will write into the executable mapping. vm_address_t writable_address = 0; vm_prot_t cur_protection, max_protection; const kern_return_t result = vm_remap(mach_task_self(), &writable_address, size, /*mask=*/alignment - 1, VM_FLAGS_ANYWHERE, mach_task_self(), reinterpret_cast(address), /*copy=*/FALSE, &cur_protection, &max_protection, VM_INHERIT_NONE); if (result != KERN_SUCCESS) { munmap(address, size); return nullptr; } NOTIFY_DEBUGGER_ABOUT_RX_PAGES(reinterpret_cast(address), size); Protect(reinterpret_cast(writable_address), size, kReadWrite); MemoryRegion region(address, size); MemoryRegion writable_alias(reinterpret_cast(writable_address), size); return new VirtualMemory(writable_alias, region, writable_alias); } #endif // defined(DART_ENABLE_RX_WORKAROUNDS) #if defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_LINUX) // PR_SET_VMA was only added to mainline Linux in 5.17, and some versions of // the Android NDK have incorrect headers, so we manually define it if absent. #if !defined(PR_SET_VMA) #define PR_SET_VMA 0x53564d41 #endif #if !defined(PR_SET_VMA_ANON_NAME) #define PR_SET_VMA_ANON_NAME 0 #endif prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, address, size, name); #endif MemoryRegion region(reinterpret_cast(address), size); return new VirtualMemory(region, region); } VirtualMemory* VirtualMemory::Reserve(intptr_t size, intptr_t alignment) { ASSERT(Utils::IsAligned(size, PageSize())); ASSERT(Utils::IsPowerOfTwo(alignment)); ASSERT(Utils::IsAligned(alignment, PageSize())); intptr_t allocated_size = size + alignment - PageSize(); void* address = GenericMapAligned(nullptr, PROT_NONE, size, alignment, allocated_size, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE); if (address == nullptr) { return nullptr; } MemoryRegion region(address, size); return new VirtualMemory(region, region); } void VirtualMemory::Commit(void* address, intptr_t size) { ASSERT(Utils::IsAligned(address, PageSize())); ASSERT(Utils::IsAligned(size, PageSize())); void* result = mmap(address, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); if (result == MAP_FAILED) { int error = errno; const int kBufferSize = 1024; char error_buf[kBufferSize]; FATAL("Failed to commit: %d (%s)", error, Utils::StrError(error, error_buf, kBufferSize)); } } void VirtualMemory::Decommit(void* address, intptr_t size) { ASSERT(Utils::IsAligned(address, PageSize())); ASSERT(Utils::IsAligned(size, PageSize())); void* result = mmap(address, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_FIXED, -1, 0); if (result == MAP_FAILED) { int error = errno; const int kBufferSize = 1024; char error_buf[kBufferSize]; FATAL("Failed to decommit: %d (%s)", error, Utils::StrError(error, error_buf, kBufferSize)); } } VirtualMemory::~VirtualMemory() { #if defined(DART_COMPRESSED_POINTERS) if (VirtualMemoryCompressedHeap::Contains(reserved_.pointer()) && (this != compressed_heap_)) { Decommit(reserved_.pointer(), reserved_.size()); VirtualMemoryCompressedHeap::Free(reserved_.pointer(), reserved_.size()); return; } #endif // defined(DART_COMPRESSED_POINTERS) if (vm_owns_region()) { Unmap(reserved_.start(), reserved_.end()); #if defined(DART_ENABLE_RX_WORKAROUNDS) if (reserved_.start() != executable_alias_.start()) { Unmap(executable_alias_.start(), executable_alias_.end()); } #endif // defined(DART_ENABLE_RX_WORKAROUNDS) } } bool VirtualMemory::FreeSubSegment(void* address, intptr_t size) { #if defined(DART_COMPRESSED_POINTERS) // Don't free the sub segment if it's managed by the compressed pointer heap. if (VirtualMemoryCompressedHeap::Contains(address)) { return false; } #endif // defined(DART_COMPRESSED_POINTERS) const uword start = reinterpret_cast(address); Unmap(start, start + size); return true; } void VirtualMemory::Protect(void* address, intptr_t size, Protection mode) { #if defined(DEBUG) Thread* thread = Thread::Current(); ASSERT(thread == nullptr || thread->IsDartMutatorThread() || thread->isolate() == nullptr || thread->isolate()->mutator_thread()->IsAtSafepoint()); #endif uword start_address = reinterpret_cast(address); uword end_address = start_address + size; uword page_address = Utils::RoundDown(start_address, PageSize()); int prot = 0; switch (mode) { case kNoAccess: prot = PROT_NONE; break; case kReadOnly: prot = PROT_READ; break; case kReadWrite: prot = PROT_READ | PROT_WRITE; break; case kReadExecute: prot = PROT_READ | PROT_EXEC; break; case kReadWriteExecute: prot = PROT_READ | PROT_WRITE | PROT_EXEC; break; } if (mprotect(reinterpret_cast(page_address), end_address - page_address, prot) != 0) { int error = errno; const int kBufferSize = 1024; char error_buf[kBufferSize]; LOG_INFO("mprotect(0x%" Px ", 0x%" Px ", %u) failed\n", page_address, end_address - page_address, prot); FATAL("mprotect failed: %d (%s)", error, Utils::StrError(error, error_buf, kBufferSize)); } LOG_INFO("mprotect(0x%" Px ", 0x%" Px ", %u) ok\n", page_address, end_address - page_address, prot); } void VirtualMemory::DontNeed(void* address, intptr_t size) { uword start_address = reinterpret_cast(address); uword end_address = start_address + size; uword page_address = Utils::RoundDown(start_address, PageSize()); #if defined(DART_HOST_OS_MACOS) int advice = MADV_FREE; #else int advice = MADV_DONTNEED; #endif if (madvise(reinterpret_cast(page_address), end_address - page_address, advice) != 0) { int error = errno; const int kBufferSize = 1024; char error_buf[kBufferSize]; FATAL("madvise failed: %d (%s)", error, Utils::StrError(error, error_buf, kBufferSize)); } } #if defined(DART_HOST_OS_MACOS) // TODO(52579): Reenable on Fuchsia. bool VirtualMemory::DuplicateRX(VirtualMemory* target) { const intptr_t aligned_size = Utils::RoundUp(size(), PageSize()); ASSERT_LESS_OR_EQUAL(aligned_size, target->size()); // Mac is special cased because iOS doesn't allow allocating new executable // memory, so the default approach would fail. We are allowed to make new // mappings of existing executable memory using vm_remap though, which is // effectively the same for non-writable memory. const mach_port_t task = mach_task_self(); const vm_address_t source_address = reinterpret_cast(address()); const vm_size_t mem_size = aligned_size; const vm_prot_t read_execute = VM_PROT_READ | VM_PROT_EXECUTE; vm_prot_t current_protection = read_execute; vm_prot_t max_protection = read_execute; vm_address_t target_address = reinterpret_cast(target->address()); kern_return_t status = vm_remap( task, &target_address, mem_size, /*mask=*/0, /*flags=*/VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, task, source_address, /*copy=*/true, ¤t_protection, &max_protection, /*inheritance=*/VM_INHERIT_NONE); if (status != KERN_SUCCESS) { return false; } ASSERT(reinterpret_cast(target_address) == target->address()); ASSERT_EQUAL(current_protection & read_execute, read_execute); ASSERT_EQUAL(max_protection & read_execute, read_execute); return true; } #endif // defined(DART_HOST_OS_MACOS) } // namespace dart #endif // defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_LINUX) || \ // defined(DART_HOST_OS_MACOS)