| // Copyright 2012 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. |
| |
| // Platform-specific code for Linux goes here. For the POSIX-compatible |
| // parts, the implementation is in platform-posix.cc. |
| |
| #include "src/base/platform/platform-linux.h" |
| |
| #include <pthread.h> |
| #include <semaphore.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/prctl.h> |
| #include <sys/resource.h> |
| #include <sys/syscall.h> |
| #include <sys/time.h> |
| |
| // Ubuntu Dapper requires memory pages to be marked as |
| // executable. Otherwise, OS raises an exception when executing code |
| // in that page. |
| #include <errno.h> |
| #include <fcntl.h> // open |
| #include <stdarg.h> |
| #include <strings.h> // index |
| #include <sys/mman.h> // mmap & munmap & mremap |
| #include <sys/stat.h> // open |
| #include <sys/sysmacros.h> |
| #include <sys/types.h> // mmap & munmap |
| #include <unistd.h> // sysconf |
| |
| #include <cmath> |
| #include <cstdio> |
| #include <memory> |
| |
| #include "src/base/logging.h" |
| #include "src/base/memory.h" |
| |
| #undef MAP_TYPE |
| |
| #include "src/base/macros.h" |
| #include "src/base/platform/platform-posix-time.h" |
| #include "src/base/platform/platform-posix.h" |
| #include "src/base/platform/platform.h" |
| |
| namespace v8 { |
| namespace base { |
| |
| TimezoneCache* OS::CreateTimezoneCache() { |
| return new PosixDefaultTimezoneCache(); |
| } |
| |
| void OS::SignalCodeMovingGC() { |
| // Support for ll_prof.py. |
| // |
| // The Linux profiler built into the kernel logs all mmap's with |
| // PROT_EXEC so that analysis tools can properly attribute ticks. We |
| // do a mmap with a name known by ll_prof.py and immediately munmap |
| // it. This injects a GC marker into the stream of events generated |
| // by the kernel and allows us to synchronize V8 code log and the |
| // kernel log. |
| long size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int) |
| FILE* f = fopen(OS::GetGCFakeMMapFile(), "w+"); |
| if (f == nullptr) { |
| OS::PrintError("Failed to open %s\n", OS::GetGCFakeMMapFile()); |
| OS::Abort(); |
| } |
| void* addr = mmap(OS::GetRandomMmapAddr(), size, PROT_READ | PROT_EXEC, |
| MAP_PRIVATE, fileno(f), 0); |
| DCHECK_NE(MAP_FAILED, addr); |
| Free(addr, size); |
| fclose(f); |
| } |
| |
| void OS::AdjustSchedulingParams() {} |
| |
| void* OS::RemapShared(void* old_address, void* new_address, size_t size) { |
| void* result = |
| mremap(old_address, 0, size, MREMAP_FIXED | MREMAP_MAYMOVE, new_address); |
| |
| if (result == MAP_FAILED) { |
| return nullptr; |
| } |
| DCHECK(result == new_address); |
| return result; |
| } |
| |
| std::vector<OS::MemoryRange> OS::GetFreeMemoryRangesWithin( |
| OS::Address boundary_start, OS::Address boundary_end, size_t minimum_size, |
| size_t alignment) { |
| std::vector<OS::MemoryRange> result = {}; |
| // This function assumes that the layout of the file is as follows: |
| // hex_start_addr-hex_end_addr rwxp <unused data> [binary_file_name] |
| // and the lines are arranged in increasing order of address. |
| // If we encounter an unexpected situation we abort scanning further entries. |
| FILE* fp = fopen("/proc/self/maps", "r"); |
| if (fp == nullptr) return {}; |
| |
| // Search for the gaps between existing virtual memory (vm) areas. If the gap |
| // contains enough space for the requested-size range that is within the |
| // boundary, push the overlapped memory range to the vector. |
| uintptr_t gap_start = 0, gap_end = 0; |
| // This loop will terminate once the scanning hits an EOF or reaches the gap |
| // at the higher address to the end of boundary. |
| uintptr_t vm_start; |
| uintptr_t vm_end; |
| while (fscanf(fp, "%" V8PRIxPTR "-%" V8PRIxPTR, &vm_start, &vm_end) == 2 && |
| gap_start < boundary_end) { |
| // Visit the gap at the lower address to this vm. |
| gap_end = vm_start; |
| // Skip the gaps at the lower address to the start of boundary. |
| if (gap_end > boundary_start) { |
| // The available area is the overlap of the gap and boundary. Push |
| // the overlapped memory range to the vector if there is enough space. |
| const uintptr_t overlap_start = |
| RoundUp(std::max(gap_start, boundary_start), alignment); |
| const uintptr_t overlap_end = |
| RoundDown(std::min(gap_end, boundary_end), alignment); |
| if (overlap_start < overlap_end && |
| overlap_end - overlap_start >= minimum_size) { |
| result.push_back({overlap_start, overlap_end}); |
| } |
| } |
| // Continue to visit the next gap. |
| gap_start = vm_end; |
| |
| int c; |
| // Skip characters until we reach the end of the line or EOF. |
| do { |
| c = getc(fp); |
| } while ((c != EOF) && (c != '\n')); |
| if (c == EOF) break; |
| } |
| |
| fclose(fp); |
| return result; |
| } |
| |
| // static |
| base::Optional<MemoryRegion> MemoryRegion::FromMapsLine(const char* line) { |
| MemoryRegion region; |
| uint8_t dev_major = 0, dev_minor = 0; |
| uintptr_t inode = 0; |
| int path_index = 0; |
| uintptr_t offset = 0; |
| // The format is: |
| // address perms offset dev inode pathname |
| // 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm |
| // |
| // The final %n term captures the offset in the input string, which is used |
| // to determine the path name. It *does not* increment the return value. |
| // Refer to man 3 sscanf for details. |
| if (sscanf(line, |
| "%" V8PRIxPTR "-%" V8PRIxPTR " %4c %" V8PRIxPTR |
| " %hhx:%hhx %" V8PRIdPTR " %n", |
| ®ion.start, ®ion.end, region.permissions, &offset, |
| &dev_major, &dev_minor, &inode, &path_index) < 7) { |
| return base::nullopt; |
| } |
| region.permissions[4] = '\0'; |
| region.inode = inode; |
| region.offset = offset; |
| region.dev = makedev(dev_major, dev_minor); |
| region.pathname.assign(line + path_index); |
| |
| return region; |
| } |
| |
| namespace { |
| // Parses /proc/self/maps. |
| std::unique_ptr<std::vector<MemoryRegion>> ParseProcSelfMaps( |
| FILE* fp, std::function<bool(const MemoryRegion&)> predicate, |
| bool early_stopping) { |
| auto result = std::make_unique<std::vector<MemoryRegion>>(); |
| |
| if (!fp) fp = fopen("/proc/self/maps", "r"); |
| if (!fp) return nullptr; |
| |
| // Allocate enough room to be able to store a full file name. |
| // 55ac243aa000-55ac243ac000 r--p 00000000 fe:01 31594735 /usr/bin/head |
| const int kMaxLineLength = 2 * FILENAME_MAX; |
| std::unique_ptr<char[]> line = std::make_unique<char[]>(kMaxLineLength); |
| |
| // This loop will terminate once the scanning hits an EOF. |
| bool error = false; |
| while (true) { |
| error = true; |
| |
| // Read to the end of the line. Exit if the read fails. |
| if (fgets(line.get(), kMaxLineLength, fp) == nullptr) { |
| if (feof(fp)) error = false; |
| break; |
| } |
| |
| size_t line_length = strlen(line.get()); |
| // Empty line at the end. |
| if (!line_length) { |
| error = false; |
| break; |
| } |
| // Line was truncated. |
| if (line.get()[line_length - 1] != '\n') break; |
| line.get()[line_length - 1] = '\0'; |
| |
| base::Optional<MemoryRegion> region = |
| MemoryRegion::FromMapsLine(line.get()); |
| if (!region) { |
| break; |
| } |
| |
| error = false; |
| |
| if (predicate(*region)) { |
| result->push_back(std::move(*region)); |
| if (early_stopping) break; |
| } |
| } |
| |
| fclose(fp); |
| if (!error && result->size()) return result; |
| |
| return nullptr; |
| } |
| |
| MemoryRegion FindEnclosingMapping(uintptr_t target_start, size_t size) { |
| auto result = ParseProcSelfMaps( |
| nullptr, |
| [=](const MemoryRegion& region) { |
| return region.start <= target_start && target_start + size < region.end; |
| }, |
| true); |
| if (result) |
| return (*result)[0]; |
| else |
| return {}; |
| } |
| } // namespace |
| |
| // static |
| std::vector<OS::SharedLibraryAddress> GetSharedLibraryAddresses(FILE* fp) { |
| auto regions = ParseProcSelfMaps( |
| fp, |
| [](const MemoryRegion& region) { |
| if (region.permissions[0] == 'r' && region.permissions[1] == '-' && |
| region.permissions[2] == 'x') { |
| return true; |
| } |
| return false; |
| }, |
| false); |
| |
| if (!regions) return {}; |
| |
| std::vector<OS::SharedLibraryAddress> result; |
| for (const MemoryRegion& region : *regions) { |
| uintptr_t start = region.start; |
| #ifdef V8_OS_ANDROID |
| if (region.pathname.size() < 4 || |
| region.pathname.compare(region.pathname.size() - 4, 4, ".apk") != 0) { |
| // Only adjust {start} based on {offset} if the file isn't the APK, |
| // since we load the library directly from the APK and don't want to |
| // apply the offset of the .so in the APK as the libraries offset. |
| start -= region.offset; |
| } |
| #else |
| start -= region.offset; |
| #endif |
| result.emplace_back(region.pathname, start, region.end); |
| } |
| return result; |
| } |
| |
| // static |
| std::vector<OS::SharedLibraryAddress> OS::GetSharedLibraryAddresses() { |
| return ::v8::base::GetSharedLibraryAddresses(nullptr); |
| } |
| |
| // static |
| bool OS::RemapPages(const void* address, size_t size, void* new_address, |
| MemoryPermission access) { |
| uintptr_t address_addr = reinterpret_cast<uintptr_t>(address); |
| |
| DCHECK(IsAligned(address_addr, AllocatePageSize())); |
| DCHECK( |
| IsAligned(reinterpret_cast<uintptr_t>(new_address), AllocatePageSize())); |
| DCHECK(IsAligned(size, AllocatePageSize())); |
| |
| MemoryRegion enclosing_region = FindEnclosingMapping(address_addr, size); |
| // Not found. |
| if (!enclosing_region.start) return false; |
| |
| // Anonymous mapping? |
| if (enclosing_region.pathname.empty()) return false; |
| |
| // Since the file is already in use for executable code, this is most likely |
| // to fail due to sandboxing, e.g. if open() is blocked outright. |
| // |
| // In Chromium on Android, the sandbox allows openat() but prohibits |
| // open(). However, the libc uses openat() in its open() wrapper, and the |
| // SELinux restrictions allow us to read from the path we want to look at, |
| // so we are in the clear. |
| // |
| // Note that this may not be allowed by the sandbox on Linux (and Chrome |
| // OS). On these systems, consider using mremap() with the MREMAP_DONTUNMAP |
| // flag. However, since we need it on non-anonymous mapping, this would only |
| // be available starting with version 5.13. |
| int fd = open(enclosing_region.pathname.c_str(), O_RDONLY); |
| if (fd == -1) return false; |
| |
| // Now we have a file descriptor to the same path the data we want to remap |
| // comes from. But... is it the *same* file? This is not guaranteed (e.g. in |
| // case of updates), so to avoid hard-to-track bugs, check that the |
| // underlying file is the same using the device number and the inode. Inodes |
| // are not unique across filesystems, and can be reused. The check works |
| // here though, since we have the problems: |
| // - Inode uniqueness: check device numbers. |
| // - Inode reuse: the initial file is still open, since we are running code |
| // from it. So its inode cannot have been reused. |
| struct stat stat_buf; |
| if (fstat(fd, &stat_buf)) { |
| close(fd); |
| return false; |
| } |
| |
| // Not the same file. |
| if (stat_buf.st_dev != enclosing_region.dev || |
| stat_buf.st_ino != enclosing_region.inode) { |
| close(fd); |
| return false; |
| } |
| |
| size_t offset_in_mapping = address_addr - enclosing_region.start; |
| size_t offset_in_file = enclosing_region.offset + offset_in_mapping; |
| int protection = GetProtectionFromMemoryPermission(access); |
| |
| void* mapped_address = mmap(new_address, size, protection, |
| MAP_FIXED | MAP_PRIVATE, fd, offset_in_file); |
| // mmap() keeps the file open. |
| close(fd); |
| |
| if (mapped_address != new_address) { |
| // Should not happen, MAP_FIXED should always map where we want. |
| UNREACHABLE(); |
| } |
| |
| return true; |
| } |
| |
| } // namespace base |
| } // namespace v8 |