// 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 POSIX goes here. This is not a platform on its // own, but contains the parts which are the same across the POSIX platforms // Linux, MacOS, FreeBSD, OpenBSD, NetBSD and QNX. #include <errno.h> #include <limits.h> #include <pthread.h> #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) #include <pthread_np.h> // for pthread_set_name_np #endif #include <sched.h> // for sched_yield #include <stdio.h> #include <time.h> #include <unistd.h> #include <sys/mman.h> #include <sys/resource.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/types.h> #if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || \ defined(__NetBSD__) || defined(__OpenBSD__) #include <sys/sysctl.h> // NOLINT, for sysctl #endif #undef MAP_TYPE #if defined(ANDROID) && !defined(V8_ANDROID_LOG_STDOUT) #define LOG_TAG "v8" #include <android/log.h> // NOLINT #endif #include <cmath> #include <cstdlib> #include "src/base/lazy-instance.h" #include "src/base/macros.h" #include "src/base/platform/platform.h" #include "src/base/platform/time.h" #include "src/base/utils/random-number-generator.h" #ifdef V8_FAST_TLS_SUPPORTED #include "src/base/atomicops.h" #endif #if V8_OS_MACOSX #include <dlfcn.h> #endif #if V8_OS_LINUX #include <sys/prctl.h> // NOLINT, for prctl #endif #ifndef _AIX #include <sys/syscall.h> #endif namespace v8 { namespace base { namespace { // 0 is never a valid thread id. const pthread_t kNoThread = (pthread_t) 0; bool g_hard_abort = false; const char* g_gc_fake_mmap = NULL; } // namespace int OS::ActivationFrameAlignment() { #if V8_TARGET_ARCH_ARM // On EABI ARM targets this is required for fp correctness in the // runtime system. return 8; #elif V8_TARGET_ARCH_MIPS return 8; #elif V8_TARGET_ARCH_S390 return 8; #else // Otherwise we just assume 16 byte alignment, i.e.: // - With gcc 4.4 the tree vectorization optimizer can generate code // that requires 16 byte alignment such as movdqa on x86. // - Mac OS X, PPC and Solaris (64-bit) activation frames must // be 16 byte-aligned; see "Mac OS X ABI Function Call Guide" return 16; #endif } intptr_t OS::CommitPageSize() { static intptr_t page_size = getpagesize(); return page_size; } void* OS::AllocateGuarded(const size_t requested) { size_t allocated = 0; const bool is_executable = false; void* mbase = OS::Allocate(requested, &allocated, is_executable); if (allocated != requested) { OS::Free(mbase, allocated); return nullptr; } if (mbase == nullptr) { return nullptr; } OS::Guard(mbase, requested); return mbase; } void OS::Free(void* address, const size_t size) { // TODO(1240712): munmap has a return value which is ignored here. int result = munmap(address, size); USE(result); DCHECK(result == 0); } // Get rid of writable permission on code allocations. void OS::ProtectCode(void* address, const size_t size) { #if V8_OS_CYGWIN DWORD old_protect; VirtualProtect(address, size, PAGE_EXECUTE_READ, &old_protect); #else mprotect(address, size, PROT_READ | PROT_EXEC); #endif } // Create guard pages. void OS::Guard(void* address, const size_t size) { #if V8_OS_CYGWIN DWORD oldprotect; VirtualProtect(address, size, PAGE_NOACCESS, &oldprotect); #else mprotect(address, size, PROT_NONE); #endif } // Make a region of memory readable and writable. void OS::Unprotect(void* address, const size_t size) { #if V8_OS_CYGWIN DWORD oldprotect; VirtualProtect(address, size, PAGE_READWRITE, &oldprotect); #else mprotect(address, size, PROT_READ | PROT_WRITE); #endif } static LazyInstance<RandomNumberGenerator>::type platform_random_number_generator = LAZY_INSTANCE_INITIALIZER; void OS::Initialize(int64_t random_seed, bool hard_abort, const char* const gc_fake_mmap) { if (random_seed) { platform_random_number_generator.Pointer()->SetSeed(random_seed); } g_hard_abort = hard_abort; g_gc_fake_mmap = gc_fake_mmap; } const char* OS::GetGCFakeMMapFile() { return g_gc_fake_mmap; } void* OS::GetRandomMmapAddr() { #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ defined(THREAD_SANITIZER) // Dynamic tools do not support custom mmap addresses. return NULL; #endif uintptr_t raw_addr; platform_random_number_generator.Pointer()->NextBytes(&raw_addr, sizeof(raw_addr)); #if V8_TARGET_ARCH_X64 // Currently available CPUs have 48 bits of virtual addressing. Truncate // the hint address to 46 bits to give the kernel a fighting chance of // fulfilling our placement request. raw_addr &= V8_UINT64_C(0x3ffffffff000); #elif V8_TARGET_ARCH_PPC64 #if V8_OS_AIX // AIX: 64 bits of virtual addressing, but we limit address range to: // a) minimize Segment Lookaside Buffer (SLB) misses and raw_addr &= V8_UINT64_C(0x3ffff000); // Use extra address space to isolate the mmap regions. raw_addr += V8_UINT64_C(0x400000000000); #elif V8_TARGET_BIG_ENDIAN // Big-endian Linux: 44 bits of virtual addressing. raw_addr &= V8_UINT64_C(0x03fffffff000); #else // Little-endian Linux: 48 bits of virtual addressing. raw_addr &= V8_UINT64_C(0x3ffffffff000); #endif #elif V8_TARGET_ARCH_S390X // Linux on Z uses bits 22-32 for Region Indexing, which translates to 42 bits // of virtual addressing. Truncate to 40 bits to allow kernel chance to // fulfill request. raw_addr &= V8_UINT64_C(0xfffffff000); #elif V8_TARGET_ARCH_S390 // 31 bits of virtual addressing. Truncate to 29 bits to allow kernel chance // to fulfill request. raw_addr &= 0x1ffff000; #else raw_addr &= 0x3ffff000; # ifdef __sun // For our Solaris/illumos mmap hint, we pick a random address in the bottom // half of the top half of the address space (that is, the third quarter). // Because we do not MAP_FIXED, this will be treated only as a hint -- the // system will not fail to mmap() because something else happens to already // be mapped at our random address. We deliberately set the hint high enough // to get well above the system's break (that is, the heap); Solaris and // illumos will try the hint and if that fails allocate as if there were // no hint at all. The high hint prevents the break from getting hemmed in // at low values, ceding half of the address space to the system heap. raw_addr += 0x80000000; #elif V8_OS_AIX // The range 0x30000000 - 0xD0000000 is available on AIX; // choose the upper range. raw_addr += 0x90000000; # else // The range 0x20000000 - 0x60000000 is relatively unpopulated across a // variety of ASLR modes (PAE kernel, NX compat mode, etc) and on macos // 10.6 and 10.7. raw_addr += 0x20000000; # endif #endif return reinterpret_cast<void*>(raw_addr); } size_t OS::AllocateAlignment() { return static_cast<size_t>(sysconf(_SC_PAGESIZE)); } void OS::Sleep(TimeDelta interval) { usleep(static_cast<useconds_t>(interval.InMicroseconds())); } void OS::Abort() { if (g_hard_abort) { V8_IMMEDIATE_CRASH(); } // Redirect to std abort to signal abnormal program termination. abort(); } void OS::DebugBreak() { #if V8_HOST_ARCH_ARM asm("bkpt 0"); #elif V8_HOST_ARCH_ARM64 asm("brk 0"); #elif V8_HOST_ARCH_MIPS asm("break"); #elif V8_HOST_ARCH_MIPS64 asm("break"); #elif V8_HOST_ARCH_PPC asm("twge 2,2"); #elif V8_HOST_ARCH_IA32 asm("int $3"); #elif V8_HOST_ARCH_X64 asm("int $3"); #elif V8_HOST_ARCH_S390 // Software breakpoint instruction is 0x0001 asm volatile(".word 0x0001"); #else #error Unsupported host architecture. #endif } class PosixMemoryMappedFile final : public OS::MemoryMappedFile { public: PosixMemoryMappedFile(FILE* file, void* memory, size_t size) : file_(file), memory_(memory), size_(size) {} ~PosixMemoryMappedFile() final; void* memory() const final { return memory_; } size_t size() const final { return size_; } private: FILE* const file_; void* const memory_; size_t const size_; }; // static OS::MemoryMappedFile* OS::MemoryMappedFile::open(const char* name) { if (FILE* file = fopen(name, "r+")) { if (fseek(file, 0, SEEK_END) == 0) { long size = ftell(file); // NOLINT(runtime/int) if (size >= 0) { void* const memory = mmap(OS::GetRandomMmapAddr(), size, PROT_READ | PROT_WRITE, MAP_SHARED, fileno(file), 0); if (memory != MAP_FAILED) { return new PosixMemoryMappedFile(file, memory, size); } } } fclose(file); } return nullptr; } // static OS::MemoryMappedFile* OS::MemoryMappedFile::create(const char* name, size_t size, void* initial) { if (FILE* file = fopen(name, "w+")) { size_t result = fwrite(initial, 1, size, file); if (result == size && !ferror(file)) { void* memory = mmap(OS::GetRandomMmapAddr(), result, PROT_READ | PROT_WRITE, MAP_SHARED, fileno(file), 0); if (memory != MAP_FAILED) { return new PosixMemoryMappedFile(file, memory, result); } } fclose(file); } return nullptr; } PosixMemoryMappedFile::~PosixMemoryMappedFile() { if (memory_) OS::Free(memory_, size_); fclose(file_); } int OS::GetCurrentProcessId() { return static_cast<int>(getpid()); } int OS::GetCurrentThreadId() { #if V8_OS_MACOSX || (V8_OS_ANDROID && defined(__APPLE__)) return static_cast<int>(pthread_mach_thread_np(pthread_self())); #elif V8_OS_LINUX return static_cast<int>(syscall(__NR_gettid)); #elif V8_OS_ANDROID return static_cast<int>(gettid()); #elif V8_OS_AIX return static_cast<int>(thread_self()); #elif V8_OS_SOLARIS return static_cast<int>(pthread_self()); #else return static_cast<int>(reinterpret_cast<intptr_t>(pthread_self())); #endif } // ---------------------------------------------------------------------------- // POSIX date/time support. // int OS::GetUserTime(uint32_t* secs, uint32_t* usecs) { struct rusage usage; if (getrusage(RUSAGE_SELF, &usage) < 0) return -1; *secs = static_cast<uint32_t>(usage.ru_utime.tv_sec); *usecs = static_cast<uint32_t>(usage.ru_utime.tv_usec); return 0; } double OS::TimeCurrentMillis() { return Time::Now().ToJsTime(); } class TimezoneCache {}; TimezoneCache* OS::CreateTimezoneCache() { return NULL; } void OS::DisposeTimezoneCache(TimezoneCache* cache) { DCHECK(cache == NULL); } void OS::ClearTimezoneCache(TimezoneCache* cache) { DCHECK(cache == NULL); } double OS::DaylightSavingsOffset(double time, TimezoneCache*) { if (std::isnan(time)) return std::numeric_limits<double>::quiet_NaN(); time_t tv = static_cast<time_t>(std::floor(time/msPerSecond)); struct tm tm; struct tm* t = localtime_r(&tv, &tm); if (NULL == t) return std::numeric_limits<double>::quiet_NaN(); return t->tm_isdst > 0 ? 3600 * msPerSecond : 0; } int OS::GetLastError() { return errno; } // ---------------------------------------------------------------------------- // POSIX stdio support. // FILE* OS::FOpen(const char* path, const char* mode) { FILE* file = fopen(path, mode); if (file == NULL) return NULL; struct stat file_stat; if (fstat(fileno(file), &file_stat) != 0) return NULL; bool is_regular_file = ((file_stat.st_mode & S_IFREG) != 0); if (is_regular_file) return file; fclose(file); return NULL; } bool OS::Remove(const char* path) { return (remove(path) == 0); } char OS::DirectorySeparator() { return '/'; } bool OS::isDirectorySeparator(const char ch) { return ch == DirectorySeparator(); } FILE* OS::OpenTemporaryFile() { return tmpfile(); } const char* const OS::LogFileOpenMode = "w"; void OS::Print(const char* format, ...) { va_list args; va_start(args, format); VPrint(format, args); va_end(args); } void OS::VPrint(const char* format, va_list args) { #if defined(ANDROID) && !defined(V8_ANDROID_LOG_STDOUT) __android_log_vprint(ANDROID_LOG_INFO, LOG_TAG, format, args); #else vprintf(format, args); #endif } void OS::FPrint(FILE* out, const char* format, ...) { va_list args; va_start(args, format); VFPrint(out, format, args); va_end(args); } void OS::VFPrint(FILE* out, const char* format, va_list args) { #if defined(ANDROID) && !defined(V8_ANDROID_LOG_STDOUT) __android_log_vprint(ANDROID_LOG_INFO, LOG_TAG, format, args); #else vfprintf(out, format, args); #endif } void OS::PrintError(const char* format, ...) { va_list args; va_start(args, format); VPrintError(format, args); va_end(args); } void OS::VPrintError(const char* format, va_list args) { #if defined(ANDROID) && !defined(V8_ANDROID_LOG_STDOUT) __android_log_vprint(ANDROID_LOG_ERROR, LOG_TAG, format, args); #else vfprintf(stderr, format, args); #endif } int OS::SNPrintF(char* str, int length, const char* format, ...) { va_list args; va_start(args, format); int result = VSNPrintF(str, length, format, args); va_end(args); return result; } int OS::VSNPrintF(char* str, int length, const char* format, va_list args) { int n = vsnprintf(str, length, format, args); if (n < 0 || n >= length) { // If the length is zero, the assignment fails. if (length > 0) str[length - 1] = '\0'; return -1; } else { return n; } } // ---------------------------------------------------------------------------- // POSIX string support. // char* OS::StrChr(char* str, int c) { return strchr(str, c); } void OS::StrNCpy(char* dest, int length, const char* src, size_t n) { strncpy(dest, src, n); } // ---------------------------------------------------------------------------- // POSIX thread support. // class Thread::PlatformData { public: PlatformData() : thread_(kNoThread) {} pthread_t thread_; // Thread handle for pthread. // Synchronizes thread creation Mutex thread_creation_mutex_; }; Thread::Thread(const Options& options) : data_(new PlatformData), stack_size_(options.stack_size()), start_semaphore_(NULL) { if (stack_size_ > 0 && static_cast<size_t>(stack_size_) < PTHREAD_STACK_MIN) { stack_size_ = PTHREAD_STACK_MIN; } set_name(options.name()); } Thread::~Thread() { delete data_; } static void SetThreadName(const char* name) { #if V8_OS_DRAGONFLYBSD || V8_OS_FREEBSD || V8_OS_OPENBSD pthread_set_name_np(pthread_self(), name); #elif V8_OS_NETBSD STATIC_ASSERT(Thread::kMaxThreadNameLength <= PTHREAD_MAX_NAMELEN_NP); pthread_setname_np(pthread_self(), "%s", name); #elif V8_OS_MACOSX // pthread_setname_np is only available in 10.6 or later, so test // for it at runtime. int (*dynamic_pthread_setname_np)(const char*); *reinterpret_cast<void**>(&dynamic_pthread_setname_np) = dlsym(RTLD_DEFAULT, "pthread_setname_np"); if (dynamic_pthread_setname_np == NULL) return; // Mac OS X does not expose the length limit of the name, so hardcode it. static const int kMaxNameLength = 63; STATIC_ASSERT(Thread::kMaxThreadNameLength <= kMaxNameLength); dynamic_pthread_setname_np(name); #elif defined(PR_SET_NAME) prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(name), // NOLINT 0, 0, 0); #endif } static void* ThreadEntry(void* arg) { Thread* thread = reinterpret_cast<Thread*>(arg); // We take the lock here to make sure that pthread_create finished first since // we don't know which thread will run first (the original thread or the new // one). { LockGuard<Mutex> lock_guard(&thread->data()->thread_creation_mutex_); } SetThreadName(thread->name()); DCHECK(thread->data()->thread_ != kNoThread); thread->NotifyStartedAndRun(); return NULL; } void Thread::set_name(const char* name) { strncpy(name_, name, sizeof(name_)); name_[sizeof(name_) - 1] = '\0'; } void Thread::Start() { int result; pthread_attr_t attr; memset(&attr, 0, sizeof(attr)); result = pthread_attr_init(&attr); DCHECK_EQ(0, result); size_t stack_size = stack_size_; if (stack_size == 0) { #if V8_OS_MACOSX // Default on Mac OS X is 512kB -- bump up to 1MB stack_size = 1 * 1024 * 1024; #elif V8_OS_AIX // Default on AIX is 96kB -- bump up to 2MB stack_size = 2 * 1024 * 1024; #endif } if (stack_size > 0) { result = pthread_attr_setstacksize(&attr, stack_size); DCHECK_EQ(0, result); } { LockGuard<Mutex> lock_guard(&data_->thread_creation_mutex_); result = pthread_create(&data_->thread_, &attr, ThreadEntry, this); } DCHECK_EQ(0, result); result = pthread_attr_destroy(&attr); DCHECK_EQ(0, result); DCHECK(data_->thread_ != kNoThread); USE(result); } void Thread::Join() { pthread_join(data_->thread_, NULL); } static Thread::LocalStorageKey PthreadKeyToLocalKey(pthread_key_t pthread_key) { #if V8_OS_CYGWIN // We need to cast pthread_key_t to Thread::LocalStorageKey in two steps // because pthread_key_t is a pointer type on Cygwin. This will probably not // work on 64-bit platforms, but Cygwin doesn't support 64-bit anyway. STATIC_ASSERT(sizeof(Thread::LocalStorageKey) == sizeof(pthread_key_t)); intptr_t ptr_key = reinterpret_cast<intptr_t>(pthread_key); return static_cast<Thread::LocalStorageKey>(ptr_key); #else return static_cast<Thread::LocalStorageKey>(pthread_key); #endif } static pthread_key_t LocalKeyToPthreadKey(Thread::LocalStorageKey local_key) { #if V8_OS_CYGWIN STATIC_ASSERT(sizeof(Thread::LocalStorageKey) == sizeof(pthread_key_t)); intptr_t ptr_key = static_cast<intptr_t>(local_key); return reinterpret_cast<pthread_key_t>(ptr_key); #else return static_cast<pthread_key_t>(local_key); #endif } #ifdef V8_FAST_TLS_SUPPORTED static Atomic32 tls_base_offset_initialized = 0; intptr_t kMacTlsBaseOffset = 0; // It's safe to do the initialization more that once, but it has to be // done at least once. static void InitializeTlsBaseOffset() { const size_t kBufferSize = 128; char buffer[kBufferSize]; size_t buffer_size = kBufferSize; int ctl_name[] = { CTL_KERN , KERN_OSRELEASE }; if (sysctl(ctl_name, 2, buffer, &buffer_size, NULL, 0) != 0) { V8_Fatal(__FILE__, __LINE__, "V8 failed to get kernel version"); } // The buffer now contains a string of the form XX.YY.ZZ, where // XX is the major kernel version component. // Make sure the buffer is 0-terminated. buffer[kBufferSize - 1] = '\0'; char* period_pos = strchr(buffer, '.'); *period_pos = '\0'; int kernel_version_major = static_cast<int>(strtol(buffer, NULL, 10)); // NOLINT // The constants below are taken from pthreads.s from the XNU kernel // sources archive at www.opensource.apple.com. if (kernel_version_major < 11) { // 8.x.x (Tiger), 9.x.x (Leopard), 10.x.x (Snow Leopard) have the // same offsets. #if V8_HOST_ARCH_IA32 kMacTlsBaseOffset = 0x48; #else kMacTlsBaseOffset = 0x60; #endif } else { // 11.x.x (Lion) changed the offset. kMacTlsBaseOffset = 0; } Release_Store(&tls_base_offset_initialized, 1); } static void CheckFastTls(Thread::LocalStorageKey key) { void* expected = reinterpret_cast<void*>(0x1234CAFE); Thread::SetThreadLocal(key, expected); void* actual = Thread::GetExistingThreadLocal(key); if (expected != actual) { V8_Fatal(__FILE__, __LINE__, "V8 failed to initialize fast TLS on current kernel"); } Thread::SetThreadLocal(key, NULL); } #endif // V8_FAST_TLS_SUPPORTED Thread::LocalStorageKey Thread::CreateThreadLocalKey() { #ifdef V8_FAST_TLS_SUPPORTED bool check_fast_tls = false; if (tls_base_offset_initialized == 0) { check_fast_tls = true; InitializeTlsBaseOffset(); } #endif pthread_key_t key; int result = pthread_key_create(&key, NULL); DCHECK_EQ(0, result); USE(result); LocalStorageKey local_key = PthreadKeyToLocalKey(key); #ifdef V8_FAST_TLS_SUPPORTED // If we just initialized fast TLS support, make sure it works. if (check_fast_tls) CheckFastTls(local_key); #endif return local_key; } void Thread::DeleteThreadLocalKey(LocalStorageKey key) { pthread_key_t pthread_key = LocalKeyToPthreadKey(key); int result = pthread_key_delete(pthread_key); DCHECK_EQ(0, result); USE(result); } void* Thread::GetThreadLocal(LocalStorageKey key) { pthread_key_t pthread_key = LocalKeyToPthreadKey(key); return pthread_getspecific(pthread_key); } void Thread::SetThreadLocal(LocalStorageKey key, void* value) { pthread_key_t pthread_key = LocalKeyToPthreadKey(key); int result = pthread_setspecific(pthread_key, value); DCHECK_EQ(0, result); USE(result); } } // namespace base } // namespace v8