v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
platform-linux.cc
Go to the documentation of this file.
1// Copyright 2012 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Platform-specific code for Linux goes here. For the POSIX-compatible
6// parts, the implementation is in platform-posix.cc.
7
9
10#include <pthread.h>
11#include <semaphore.h>
12#include <signal.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <sys/prctl.h>
16#include <sys/resource.h>
17#include <sys/syscall.h>
18#include <sys/time.h>
19
20// Ubuntu Dapper requires memory pages to be marked as
21// executable. Otherwise, OS raises an exception when executing code
22// in that page.
23#include <errno.h>
24#include <fcntl.h> // open
25#include <stdarg.h>
26#include <strings.h> // index
27#include <sys/mman.h> // mmap & munmap & mremap
28#include <sys/stat.h> // open
29#include <sys/sysmacros.h>
30#include <sys/types.h> // mmap & munmap
31#include <unistd.h> // sysconf
32
33#include <cmath>
34#include <cstdio>
35#include <memory>
36#include <optional>
37
38#include "src/base/logging.h"
39#include "src/base/memory.h"
40
41#undef MAP_TYPE
42
43#include "src/base/macros.h"
47
48namespace v8 {
49namespace base {
50
51TimezoneCache* OS::CreateTimezoneCache() {
52 return new PosixDefaultTimezoneCache();
53}
54
56 // Support for ll_prof.py.
57 //
58 // The Linux profiler built into the kernel logs all mmap's with
59 // PROT_EXEC so that analysis tools can properly attribute ticks. We
60 // do a mmap with a name known by ll_prof.py and immediately munmap
61 // it. This injects a GC marker into the stream of events generated
62 // by the kernel and allows us to synchronize V8 code log and the
63 // kernel log.
64 long size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
65 FILE* f = fopen(OS::GetGCFakeMMapFile(), "w+");
66 if (f == nullptr) {
67 OS::PrintError("Failed to open %s\n", OS::GetGCFakeMMapFile());
68 OS::Abort();
69 }
70 void* addr = mmap(OS::GetRandomMmapAddr(), size, PROT_READ | PROT_EXEC,
71 MAP_PRIVATE, fileno(f), 0);
72 DCHECK_NE(MAP_FAILED, addr);
73 Free(addr, size);
74 fclose(f);
75}
76
78
79void* OS::RemapShared(void* old_address, void* new_address, size_t size) {
80 void* result =
81 mremap(old_address, 0, size, MREMAP_FIXED | MREMAP_MAYMOVE, new_address);
82
83 if (result == MAP_FAILED) {
84 return nullptr;
85 }
86 DCHECK(result == new_address);
87 return result;
88}
89
90std::optional<OS::MemoryRange> OS::GetFirstFreeMemoryRangeWithin(
91 OS::Address boundary_start, OS::Address boundary_end, size_t minimum_size,
92 size_t alignment) {
93 std::optional<OS::MemoryRange> result;
94 // This function assumes that the layout of the file is as follows:
95 // hex_start_addr-hex_end_addr rwxp <unused data> [binary_file_name]
96 // and the lines are arranged in increasing order of address.
97 // If we encounter an unexpected situation we abort scanning further entries.
98 FILE* fp = fopen("/proc/self/maps", "r");
99 if (fp == nullptr) return {};
100
101 // Search for the gaps between existing virtual memory (vm) areas. If the gap
102 // contains enough space for the requested-size range that is within the
103 // boundary, push the overlapped memory range to the vector.
104 uintptr_t gap_start = 0, gap_end = 0;
105 // This loop will terminate once the scanning hits an EOF or reaches the gap
106 // at the higher address to the end of boundary.
107 uintptr_t vm_start;
108 uintptr_t vm_end;
109 while (fscanf(fp, "%" V8PRIxPTR "-%" V8PRIxPTR, &vm_start, &vm_end) == 2 &&
110 gap_start < boundary_end) {
111 // Visit the gap at the lower address to this vm.
112 gap_end = vm_start;
113 // Skip the gaps at the lower address to the start of boundary.
114 if (gap_end > boundary_start) {
115 // The available area is the overlap of the gap and boundary. Push
116 // the overlapped memory range to the vector if there is enough space.
117 const uintptr_t overlap_start =
118 RoundUp(std::max(gap_start, boundary_start), alignment);
119 const uintptr_t overlap_end =
120 RoundDown(std::min(gap_end, boundary_end), alignment);
121 if (overlap_start < overlap_end &&
122 overlap_end - overlap_start >= minimum_size) {
123 result = {overlap_start, overlap_end};
124 break;
125 }
126 }
127 // Continue to visit the next gap.
128 gap_start = vm_end;
129
130 int c;
131 // Skip characters until we reach the end of the line or EOF.
132 do {
133 c = getc(fp);
134 } while ((c != EOF) && (c != '\n'));
135 if (c == EOF) break;
136 }
137
138 fclose(fp);
139 return result;
140}
141
142// static
143std::optional<MemoryRegion> MemoryRegion::FromMapsLine(const char* line) {
144 MemoryRegion region;
145 unsigned dev_major = 0, dev_minor = 0;
146 uintptr_t inode = 0;
147 int path_index = 0;
148 uintptr_t offset = 0;
149 // The format is:
150 // address perms offset dev inode pathname
151 // 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm
152 //
153 // The final %n term captures the offset in the input string, which is used
154 // to determine the path name. It *does not* increment the return value.
155 // Refer to man 3 sscanf for details.
156 if (sscanf(line,
157 "%" V8PRIxPTR "-%" V8PRIxPTR " %4c %" V8PRIxPTR
158 " %x:%x %" V8PRIdPTR " %n",
159 &region.start, &region.end, region.permissions, &offset,
160 &dev_major, &dev_minor, &inode, &path_index) < 7) {
161 return std::nullopt;
162 }
163 region.permissions[4] = '\0';
164 region.inode = inode;
165 region.offset = offset;
166 region.dev = makedev(dev_major, dev_minor);
167 region.pathname.assign(line + path_index);
168
169 return region;
170}
171
172namespace {
173// Parses /proc/self/maps.
174std::unique_ptr<std::vector<MemoryRegion>> ParseProcSelfMaps(
175 FILE* fp, std::function<bool(const MemoryRegion&)> predicate,
176 bool early_stopping) {
177 auto result = std::make_unique<std::vector<MemoryRegion>>();
178
179 if (!fp) fp = fopen("/proc/self/maps", "r");
180 if (!fp) return nullptr;
181
182 // Allocate enough room to be able to store a full file name.
183 // 55ac243aa000-55ac243ac000 r--p 00000000 fe:01 31594735 /usr/bin/head
184 const int kMaxLineLength = 2 * FILENAME_MAX;
185 std::unique_ptr<char[]> line = std::make_unique<char[]>(kMaxLineLength);
186
187 // This loop will terminate once the scanning hits an EOF.
188 bool error = false;
189 while (true) {
190 error = true;
191
192 // Read to the end of the line. Exit if the read fails.
193 if (fgets(line.get(), kMaxLineLength, fp) == nullptr) {
194 if (feof(fp)) error = false;
195 break;
196 }
197
198 size_t line_length = strlen(line.get());
199 // Empty line at the end.
200 if (!line_length) {
201 error = false;
202 break;
203 }
204 // Line was truncated.
205 if (line.get()[line_length - 1] != '\n') break;
206 line.get()[line_length - 1] = '\0';
207
208 std::optional<MemoryRegion> region = MemoryRegion::FromMapsLine(line.get());
209 if (!region) {
210 break;
211 }
212
213 error = false;
214
215 if (predicate(*region)) {
216 result->push_back(std::move(*region));
217 if (early_stopping) break;
218 }
219 }
220
221 fclose(fp);
222 if (!error && !result->empty()) return result;
223
224 return nullptr;
225}
226
227MemoryRegion FindEnclosingMapping(uintptr_t target_start, size_t size) {
228 auto result = ParseProcSelfMaps(
229 nullptr,
230 [=](const MemoryRegion& region) {
231 return region.start <= target_start && target_start + size < region.end;
232 },
233 true);
234 if (result)
235 return (*result)[0];
236 else
237 return {};
238}
239} // namespace
240
241// static
242std::vector<OS::SharedLibraryAddress> GetSharedLibraryAddresses(FILE* fp) {
243 auto regions = ParseProcSelfMaps(
244 fp,
245 [](const MemoryRegion& region) {
246 if (region.permissions[0] == 'r' && region.permissions[1] == '-' &&
247 region.permissions[2] == 'x') {
248 return true;
249 }
250 return false;
251 },
252 false);
253
254 if (!regions) return {};
255
256 std::vector<OS::SharedLibraryAddress> result;
257 for (const MemoryRegion& region : *regions) {
258 uintptr_t start = region.start;
259#ifdef V8_OS_ANDROID
260 if (region.pathname.size() < 4 ||
261 region.pathname.compare(region.pathname.size() - 4, 4, ".apk") != 0) {
262 // Only adjust {start} based on {offset} if the file isn't the APK,
263 // since we load the library directly from the APK and don't want to
264 // apply the offset of the .so in the APK as the libraries offset.
265 start -= region.offset;
266 }
267#else
268 start -= region.offset;
269#endif
270 result.emplace_back(region.pathname, start, region.end);
271 }
272 return result;
273}
274
275// static
276std::vector<OS::SharedLibraryAddress> OS::GetSharedLibraryAddresses() {
277 return ::v8::base::GetSharedLibraryAddresses(nullptr);
278}
279
280// static
281bool OS::RemapPages(const void* address, size_t size, void* new_address,
282 MemoryPermission access) {
283 uintptr_t address_addr = reinterpret_cast<uintptr_t>(address);
284
285 DCHECK(IsAligned(address_addr, AllocatePageSize()));
286 DCHECK(
287 IsAligned(reinterpret_cast<uintptr_t>(new_address), AllocatePageSize()));
289
290 MemoryRegion enclosing_region = FindEnclosingMapping(address_addr, size);
291 // Not found.
292 if (!enclosing_region.start) return false;
293
294 // Anonymous mapping?
295 if (enclosing_region.pathname.empty()) return false;
296
297 // Since the file is already in use for executable code, this is most likely
298 // to fail due to sandboxing, e.g. if open() is blocked outright.
299 //
300 // In Chromium on Android, the sandbox allows openat() but prohibits
301 // open(). However, the libc uses openat() in its open() wrapper, and the
302 // SELinux restrictions allow us to read from the path we want to look at,
303 // so we are in the clear.
304 //
305 // Note that this may not be allowed by the sandbox on Linux (and Chrome
306 // OS). On these systems, consider using mremap() with the MREMAP_DONTUNMAP
307 // flag. However, since we need it on non-anonymous mapping, this would only
308 // be available starting with version 5.13.
309 int fd = open(enclosing_region.pathname.c_str(), O_RDONLY);
310 if (fd == -1) return false;
311
312 // Now we have a file descriptor to the same path the data we want to remap
313 // comes from. But... is it the *same* file? This is not guaranteed (e.g. in
314 // case of updates), so to avoid hard-to-track bugs, check that the
315 // underlying file is the same using the device number and the inode. Inodes
316 // are not unique across filesystems, and can be reused. The check works
317 // here though, since we have the problems:
318 // - Inode uniqueness: check device numbers.
319 // - Inode reuse: the initial file is still open, since we are running code
320 // from it. So its inode cannot have been reused.
321 struct stat stat_buf;
322 if (fstat(fd, &stat_buf)) {
323 close(fd);
324 return false;
325 }
326
327 // Not the same file.
328 if (stat_buf.st_dev != enclosing_region.dev ||
329 stat_buf.st_ino != enclosing_region.inode) {
330 close(fd);
331 return false;
332 }
333
334 size_t offset_in_mapping = address_addr - enclosing_region.start;
335 size_t offset_in_file = enclosing_region.offset + offset_in_mapping;
336 int protection = GetProtectionFromMemoryPermission(access);
337
338 void* mapped_address = mmap(new_address, size, protection,
339 MAP_FIXED | MAP_PRIVATE, fd, offset_in_file);
340 // mmap() keeps the file open.
341 close(fd);
342
343 if (mapped_address != new_address) {
344 // Should not happen, MAP_FIXED should always map where we want.
345 UNREACHABLE();
346 }
347
348 return true;
349}
350
351} // namespace base
352} // namespace v8
static void * GetRandomMmapAddr()
static V8_WARN_UNUSED_RESULT void * RemapShared(void *old_address, void *new_address, size_t size)
static void SignalCodeMovingGC()
static size_t AllocatePageSize()
static void Abort()
static std::vector< SharedLibraryAddress > GetSharedLibraryAddresses()
uintptr_t Address
Definition platform.h:315
static V8_WARN_UNUSED_RESULT bool RemapPages(const void *address, size_t size, void *new_address, MemoryPermission access)
static TimezoneCache * CreateTimezoneCache()
static void Free(void *address, size_t size)
static std::optional< MemoryRange > GetFirstFreeMemoryRangeWithin(Address boundary_start, Address boundary_end, size_t minimum_size, size_t alignment)
static void AdjustSchedulingParams()
int start
ZoneVector< RpoNumber > & result
int GetProtectionFromMemoryPermission(OS::MemoryPermission access)
std::vector< OS::SharedLibraryAddress > GetSharedLibraryAddresses(FILE *fp)
#define UNREACHABLE()
Definition logging.h:67
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK(condition)
Definition logging.h:482
constexpr T RoundUp(T x, intptr_t m)
Definition macros.h:387
#define V8PRIdPTR
Definition macros.h:332
constexpr T RoundDown(T x, intptr_t m)
Definition macros.h:371
constexpr bool IsAligned(T value, U alignment)
Definition macros.h:403
#define V8PRIxPTR
Definition macros.h:331
static std::optional< MemoryRegion > FromMapsLine(const char *line)