// 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(TARGET_OS_LINUX) #include "vm/os.h" #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include "platform/utils.h" #include "vm/code_observers.h" #include "vm/dart.h" #include "vm/debuginfo.h" #include "vm/isolate.h" #include "vm/thread.h" #include "vm/vtune.h" #include "vm/zone.h" namespace dart { // Linux CodeObservers. DEFINE_FLAG(bool, generate_gdb_symbols, false, "Generate symbols of generated dart functions for debugging with GDB"); DEFINE_FLAG(bool, generate_perf_events_symbols, false, "Generate events symbols for profiling with perf"); DEFINE_FLAG(bool, ll_prof, false, "Generate compiled code log file for processing with ll_prof.py."); DEFINE_FLAG(charp, generate_pprof_symbols, NULL, "Writes pprof events symbols to the provided file"); DEFINE_FLAG(bool, generate_perf_jitdump, false, "Writes jitdump data for profiling with perf annotate"); class LowLevelProfileCodeObserver : public CodeObserver { public: LowLevelProfileCodeObserver() { Dart_FileOpenCallback file_open = Isolate::file_open_callback(); if (file_open == NULL) { return; } const char* filename = "v8.log.ll"; log_file_ = (*file_open)(filename, true); #if defined(TARGET_ARCH_IA32) const char arch[] = "ia32"; #elif defined(TARGET_ARCH_X64) const char arch[] = "x64"; #elif defined(TARGET_ARCH_ARM) const char arch[] = "arm"; #elif defined(TARGET_ARCH_ARM64) const char arch[] = "arm64"; #elif defined(TARGET_ARCH_MIPS) const char arch[] = "mips"; #else #error Unknown architecture. #endif LowLevelLogWriteBytes(arch, sizeof(arch)); } ~LowLevelProfileCodeObserver() { Dart_FileCloseCallback file_close = Isolate::file_close_callback(); if (file_close == NULL) { return; } ASSERT(log_file_ != NULL); (*file_close)(log_file_); } virtual bool IsActive() const { return FLAG_ll_prof; } struct LowLevelCodeCreateStruct { static const char kTag = 'C'; int32_t name_size; uword code_address; int32_t code_size; }; template void LowLevelLogWriteStruct(const T& s) { char tag = T::kTag; LowLevelLogWriteBytes(reinterpret_cast(&tag), sizeof(tag)); LowLevelLogWriteBytes(reinterpret_cast(&s), sizeof(s)); } void LowLevelLogWriteBytes(const char* bytes, int size) { Dart_FileWriteCallback file_write = Isolate::file_write_callback(); ASSERT(file_write != NULL); (file_write)(bytes, size, log_file_); } virtual void Notify(const char* name, uword base, uword prologue_offset, uword size, bool optimized) { const char* marker = optimized ? "*" : ""; char* name_buffer = Isolate::Current()->current_zone()->PrintToString("%s%s", marker, name); intptr_t len = strlen(name_buffer); LowLevelCodeCreateStruct event; event.name_size = len; event.code_address = base; event.code_size = size; { MutexLocker ml(CodeObservers::mutex()); LowLevelLogWriteStruct(event); LowLevelLogWriteBytes(name_buffer, len); LowLevelLogWriteBytes(reinterpret_cast(base), size); } } private: void* log_file_; DISALLOW_COPY_AND_ASSIGN(LowLevelProfileCodeObserver); }; class PerfCodeObserver : public CodeObserver { public: PerfCodeObserver() { Dart_FileOpenCallback file_open = Isolate::file_open_callback(); if (file_open == NULL) { return; } const char* format = "/tmp/perf-%" Pd ".map"; intptr_t pid = getpid(); intptr_t len = OS::SNPrint(NULL, 0, format, pid); char* filename = new char[len + 1]; OS::SNPrint(filename, len + 1, format, pid); out_file_ = (*file_open)(filename, true); delete[] filename; } ~PerfCodeObserver() { Dart_FileCloseCallback file_close = Isolate::file_close_callback(); if (file_close == NULL) { return; } ASSERT(out_file_ != NULL); (*file_close)(out_file_); } virtual bool IsActive() const { return FLAG_generate_perf_events_symbols; } virtual void Notify(const char* name, uword base, uword prologue_offset, uword size, bool optimized) { Dart_FileWriteCallback file_write = Isolate::file_write_callback(); ASSERT(file_write != NULL); const char* format = "%" Px " %" Px " %s%s\n"; const char* marker = optimized ? "*" : ""; intptr_t len = OS::SNPrint(NULL, 0, format, base, size, marker, name); char* buffer = Isolate::Current()->current_zone()->Alloc(len + 1); OS::SNPrint(buffer, len + 1, format, base, size, marker, name); ASSERT(out_file_ != NULL); { MutexLocker ml(CodeObservers::mutex()); (*file_write)(buffer, len, out_file_); } } private: void* out_file_; DISALLOW_COPY_AND_ASSIGN(PerfCodeObserver); }; class PprofCodeObserver : public CodeObserver { public: PprofCodeObserver() { pprof_symbol_generator_ = DebugInfo::NewGenerator(); } ~PprofCodeObserver() { Dart_FileOpenCallback file_open = Isolate::file_open_callback(); if (file_open == NULL) { return; } Dart_FileCloseCallback file_close = Isolate::file_close_callback(); if (file_close == NULL) { return; } Dart_FileWriteCallback file_write = Isolate::file_write_callback(); if (file_write == NULL) { return; } if (FLAG_generate_pprof_symbols == NULL) { return; } const char* filename = FLAG_generate_pprof_symbols; void* out_file = (*file_open)(filename, true); ASSERT(out_file != NULL); DebugInfo::ByteBuffer* debug_region = new DebugInfo::ByteBuffer(); ASSERT(debug_region != NULL); pprof_symbol_generator_->WriteToMemory(debug_region); int buffer_size = debug_region->size(); void* buffer = debug_region->data(); if (buffer_size > 0) { MutexLocker ml(CodeObservers::mutex()); ASSERT(buffer != NULL); (*file_write)(buffer, buffer_size, out_file); } delete debug_region; (*file_close)(out_file); DebugInfo::UnregisterAllSections(); } virtual bool IsActive() const { return FLAG_generate_pprof_symbols != NULL; } virtual void Notify(const char* name, uword base, uword prologue_offset, uword size, bool optimized) { ASSERT(pprof_symbol_generator_ != NULL); pprof_symbol_generator_->AddCode(base, size); pprof_symbol_generator_->AddCodeRegion(name, base, size); } private: DebugInfo* pprof_symbol_generator_; DISALLOW_COPY_AND_ASSIGN(PprofCodeObserver); }; class GdbCodeObserver : public CodeObserver { public: GdbCodeObserver() { } virtual bool IsActive() const { return FLAG_generate_gdb_symbols; } virtual void Notify(const char* name, uword base, uword prologue_offset, uword size, bool optimized) { if (prologue_offset > 0) { // In order to ensure that gdb sees the first instruction of a function // as the prologue sequence we register two symbols for the cases when // the prologue sequence is not the first instruction: // _entry is used for code preceding the prologue sequence. // for rest of the code (first instruction is prologue sequence). const char* kFormat = "%s_%s"; intptr_t len = OS::SNPrint(NULL, 0, kFormat, name, "entry"); char* pname = Isolate::Current()->current_zone()->Alloc(len + 1); OS::SNPrint(pname, (len + 1), kFormat, name, "entry"); DebugInfo::RegisterSection(pname, base, size); DebugInfo::RegisterSection(name, (base + prologue_offset), (size - prologue_offset)); } else { DebugInfo::RegisterSection(name, base, size); } } private: DISALLOW_COPY_AND_ASSIGN(GdbCodeObserver); }; #define CLOCKFD 3 #define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD) // NOLINT class JitdumpCodeObserver : public CodeObserver { public: JitdumpCodeObserver() { ASSERT(FLAG_generate_perf_jitdump); out_file_ = NULL; clock_fd_ = -1; clock_id_ = kInvalidClockId; code_sequence_ = 0; Dart_FileOpenCallback file_open = Isolate::file_open_callback(); Dart_FileWriteCallback file_write = Isolate::file_write_callback(); Dart_FileCloseCallback file_close = Isolate::file_close_callback(); if ((file_open == NULL) || (file_write == NULL) || (file_close == NULL)) { return; } // The Jitdump code observer writes all jitted code into // /tmp/jit-.dump, we open the file once on initialization and close // it when the VM is going down. { // Open the file. const char* format = "/tmp/jit-%" Pd ".dump"; intptr_t pid = getpid(); intptr_t len = OS::SNPrint(NULL, 0, format, pid); char* filename = new char[len + 1]; OS::SNPrint(filename, len + 1, format, pid); out_file_ = (*file_open)(filename, true); ASSERT(out_file_ != NULL); // Write the jit dump header. WriteHeader(); } // perf uses an internal clock and because our output is merged with data // collected by perf our timestamps must be consistent. Using // the posix-clock-module (/dev/trace_clock) as our time source ensures // we are consistent with the perf timestamps. clock_id_ = kInvalidClockId; clock_fd_ = open("/dev/trace_clock", O_RDONLY); if (clock_fd_ >= 0) { clock_id_ = FD_TO_CLOCKID(clock_fd_); } } ~JitdumpCodeObserver() { Dart_FileCloseCallback file_close = Isolate::file_close_callback(); if (file_close == NULL) { return; } ASSERT(out_file_ != NULL); (*file_close)(out_file_); if (clock_fd_ >= 0) { close(clock_fd_); } } virtual bool IsActive() const { return FLAG_generate_perf_jitdump && (out_file_ != NULL); } virtual void Notify(const char* name, uword base, uword prologue_offset, uword size, bool optimized) { WriteCodeLoad(name, base, prologue_offset, size, optimized); } private: static const uint32_t kJitHeaderMagic = 0x4F74496A; static const uint32_t kJitHeaderVersion = 0x2; static const uint32_t kElfMachIA32 = 3; static const uint32_t kElfMachX64 = 62; static const uint32_t kElfMachARM = 40; // TODO(zra): Find the right ARM64 constant. static const uint32_t kElfMachARM64 = 40; static const uint32_t kElfMachMIPS = 10; static const int kInvalidClockId = -1; struct jitheader { uint32_t magic; uint32_t version; uint32_t total_size; uint32_t elf_mach; uint32_t pad1; uint32_t pid; uint64_t timestamp; }; enum jit_record_type { JIT_CODE_LOAD = 0, /* JIT_CODE_UNLOAD = 1, */ /* JIT_CODE_CLOSE = 2, */ /* JIT_CODE_DEBUG_INFO = 3, */ JIT_CODE_MAX = 4, }; struct jr_code_load { uint32_t id; uint32_t total_size; uint64_t timestamp; uint32_t pid; uint32_t tid; uint64_t vma; uint64_t code_addr; uint32_t code_size; uint64_t code_index; uint32_t align; }; const char* GenerateCodeName(const char* name, bool optimized) { const char* format = "%s%s"; const char* marker = optimized ? "*" : ""; intptr_t len = OS::SNPrint(NULL, 0, format, marker, name); char* buffer = Isolate::Current()->current_zone()->Alloc(len + 1); OS::SNPrint(buffer, len + 1, format, marker, name); return buffer; } uint32_t GetElfMach() { #if defined(TARGET_ARCH_IA32) return kElfMachIA32; #elif defined(TARGET_ARCH_X64) return kElfMachX64; #elif defined(TARGET_ARCH_ARM) return kElfMachARM; #elif defined(TARGET_ARCH_ARM64) return kElfMachARM64; #elif defined(TARGET_ARCH_MIPS) return kElfMachMIPS; #else #error Unknown architecture. #endif } pid_t gettid() { // libc doesn't wrap the Linux-specific gettid system call. // Note that this thread id is not the same as the posix thread id. return syscall(SYS_gettid); } uint64_t GetKernelTimeNanos() { if (clock_id_ != kInvalidClockId) { struct timespec ts; int r = clock_gettime(clock_id_, &ts); ASSERT(r == 0); uint64_t nanos = static_cast(ts.tv_sec) * static_cast(kNanosecondsPerSecond); nanos += static_cast(ts.tv_nsec); return nanos; } else { return OS::GetCurrentTimeMicros() * kNanosecondsPerMicrosecond; } } void WriteHeader() { Dart_FileWriteCallback file_write = Isolate::file_write_callback(); ASSERT(file_write != NULL); ASSERT(out_file_ != NULL); jitheader header; header.magic = kJitHeaderMagic; header.version = kJitHeaderVersion; header.total_size = sizeof(jitheader); header.pad1 = 0xdeadbeef; header.elf_mach = GetElfMach(); header.pid = getpid(); header.timestamp = GetKernelTimeNanos(); { MutexLocker ml(CodeObservers::mutex()); (*file_write)(&header, sizeof(header), out_file_); } } void WriteCodeLoad(const char* name, uword base, uword prologue_offset, uword code_size, bool optimized) { Dart_FileWriteCallback file_write = Isolate::file_write_callback(); ASSERT(file_write != NULL); ASSERT(out_file_ != NULL); const char* code_name = GenerateCodeName(name, optimized); const intptr_t code_name_size = strlen(code_name) + 1; uint8_t* code_pointer = reinterpret_cast(base); jr_code_load code_load; code_load.id = JIT_CODE_LOAD; code_load.total_size = sizeof(code_load) + code_name_size + code_size; code_load.timestamp = GetKernelTimeNanos(); code_load.pid = getpid(); code_load.tid = gettid(); code_load.vma = 0x0; // Our addresses are absolute. code_load.code_addr = base; code_load.code_size = code_size; code_load.align = OS::PreferredCodeAlignment(); { MutexLocker ml(CodeObservers::mutex()); // Set this field under the index. code_load.code_index = code_sequence_++; // Write structures. (*file_write)(&code_load, sizeof(code_load), out_file_); (*file_write)(code_name, code_name_size, out_file_); (*file_write)(code_pointer, code_size, out_file_); } } void* out_file_; int clock_fd_; int clock_id_; uint64_t code_sequence_; DISALLOW_COPY_AND_ASSIGN(JitdumpCodeObserver); }; const char* OS::Name() { return "linux"; } intptr_t OS::ProcessId() { return static_cast(getpid()); } static bool LocalTime(int64_t seconds_since_epoch, tm* tm_result) { time_t seconds = static_cast(seconds_since_epoch); if (seconds != seconds_since_epoch) return false; struct tm* error_code = localtime_r(&seconds, tm_result); return error_code != NULL; } const char* OS::GetTimeZoneName(int64_t seconds_since_epoch) { tm decomposed; bool succeeded = LocalTime(seconds_since_epoch, &decomposed); // If unsuccessful, return an empty string like V8 does. return (succeeded && (decomposed.tm_zone != NULL)) ? decomposed.tm_zone : ""; } int OS::GetTimeZoneOffsetInSeconds(int64_t seconds_since_epoch) { tm decomposed; bool succeeded = LocalTime(seconds_since_epoch, &decomposed); // Even if the offset was 24 hours it would still easily fit into 32 bits. // If unsuccessful, return zero like V8 does. return succeeded ? static_cast(decomposed.tm_gmtoff) : 0; } int OS::GetLocalTimeZoneAdjustmentInSeconds() { // TODO(floitsch): avoid excessive calls to tzset? tzset(); // Even if the offset was 24 hours it would still easily fit into 32 bits. // Note that Unix and Dart disagree on the sign. return static_cast(-timezone); } int64_t OS::GetCurrentTimeMillis() { return GetCurrentTimeMicros() / 1000; } int64_t OS::GetCurrentTimeMicros() { // gettimeofday has microsecond resolution. struct timeval tv; if (gettimeofday(&tv, NULL) < 0) { UNREACHABLE(); return 0; } return (static_cast(tv.tv_sec) * 1000000) + tv.tv_usec; } void* OS::AlignedAllocate(intptr_t size, intptr_t alignment) { const int kMinimumAlignment = 16; ASSERT(Utils::IsPowerOfTwo(alignment)); ASSERT(alignment >= kMinimumAlignment); void* p = memalign(alignment, size); if (p == NULL) { UNREACHABLE(); } return p; } void OS::AlignedFree(void* ptr) { free(ptr); } // TODO(5411554): May need to hoist these architecture dependent code // into a architecture specific file e.g: os_ia32_linux.cc word OS::ActivationFrameAlignment() { #if defined(TARGET_ARCH_IA32) || \ defined(TARGET_ARCH_X64) || \ defined(TARGET_ARCH_ARM64) const int kMinimumAlignment = 16; #elif defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_MIPS) const int kMinimumAlignment = 8; #else #error Unsupported architecture. #endif word alignment = kMinimumAlignment; // TODO(5411554): Allow overriding default stack alignment for // testing purposes. // Flags::DebugIsInt("stackalign", &alignment); ASSERT(Utils::IsPowerOfTwo(alignment)); ASSERT(alignment >= kMinimumAlignment); return alignment; } word OS::PreferredCodeAlignment() { #if defined(TARGET_ARCH_IA32) || \ defined(TARGET_ARCH_X64) || \ defined(TARGET_ARCH_ARM64) const int kMinimumAlignment = 32; #elif defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_MIPS) const int kMinimumAlignment = 16; #else #error Unsupported architecture. #endif word alignment = kMinimumAlignment; // TODO(5411554): Allow overriding default code alignment for // testing purposes. // Flags::DebugIsInt("codealign", &alignment); ASSERT(Utils::IsPowerOfTwo(alignment)); ASSERT(alignment >= kMinimumAlignment); ASSERT(alignment <= OS::kMaxPreferredCodeAlignment); return alignment; } uword OS::GetStackSizeLimit() { struct rlimit stack_limit; int retval = getrlimit(RLIMIT_STACK, &stack_limit); ASSERT(retval == 0); if (stack_limit.rlim_cur > INT_MAX) { retval = INT_MAX; } else { retval = stack_limit.rlim_cur; } return retval; } int OS::NumberOfAvailableProcessors() { return sysconf(_SC_NPROCESSORS_ONLN); } void OS::Sleep(int64_t millis) { int64_t micros = millis * kMicrosecondsPerMillisecond; SleepMicros(micros); } void OS::SleepMicros(int64_t micros) { struct timespec req; // requested. struct timespec rem; // remainder. int64_t seconds = micros / kMicrosecondsPerSecond; micros = micros - seconds * kMicrosecondsPerSecond; int64_t nanos = micros * kNanosecondsPerMicrosecond; req.tv_sec = seconds; req.tv_nsec = nanos; while (true) { int r = nanosleep(&req, &rem); if (r == 0) { break; } // We should only ever see an interrupt error. ASSERT(errno == EINTR); // Copy remainder into requested and repeat. req = rem; } } void OS::DebugBreak() { #if defined(HOST_ARCH_X64) || defined(HOST_ARCH_IA32) asm("int $3"); #elif defined(HOST_ARCH_ARM) asm("svc #0x9f0001"); // __ARM_NR_breakpoint #elif defined(HOST_ARCH_MIPS) UNIMPLEMENTED(); #else #error Unsupported architecture. #endif } char* OS::StrNDup(const char* s, intptr_t n) { return strndup(s, n); } void OS::Print(const char* format, ...) { va_list args; va_start(args, format); VFPrint(stdout, format, args); va_end(args); } void OS::VFPrint(FILE* stream, const char* format, va_list args) { vfprintf(stream, format, args); fflush(stream); } int OS::SNPrint(char* str, size_t size, const char* format, ...) { va_list args; va_start(args, format); int retval = VSNPrint(str, size, format, args); va_end(args); return retval; } int OS::VSNPrint(char* str, size_t size, const char* format, va_list args) { int retval = vsnprintf(str, size, format, args); if (retval < 0) { FATAL1("Fatal error in OS::VSNPrint with format '%s'", format); } return retval; } bool OS::StringToInt64(const char* str, int64_t* value) { ASSERT(str != NULL && strlen(str) > 0 && value != NULL); int32_t base = 10; char* endptr; int i = 0; if (str[0] == '-') { i = 1; } if ((str[i] == '0') && (str[i + 1] == 'x' || str[i + 1] == 'X') && (str[i + 2] != '\0')) { base = 16; } errno = 0; *value = strtoll(str, &endptr, base); return ((errno == 0) && (endptr != str) && (*endptr == 0)); } void OS::RegisterCodeObservers() { if (FLAG_ll_prof) { CodeObservers::Register(new LowLevelProfileCodeObserver); } if (FLAG_generate_perf_events_symbols) { CodeObservers::Register(new PerfCodeObserver); } if (FLAG_generate_gdb_symbols) { CodeObservers::Register(new GdbCodeObserver); } if (FLAG_generate_pprof_symbols != NULL) { CodeObservers::Register(new PprofCodeObserver); } if (FLAG_generate_perf_jitdump) { CodeObservers::Register(new JitdumpCodeObserver); } #if defined(DART_VTUNE_SUPPORT) CodeObservers::Register(new VTuneCodeObserver); #endif } void OS::PrintErr(const char* format, ...) { va_list args; va_start(args, format); VFPrint(stderr, format, args); va_end(args); } void OS::InitOnce() { // TODO(5411554): For now we check that initonce is called only once, // Once there is more formal mechanism to call InitOnce we can move // this check there. static bool init_once_called = false; ASSERT(init_once_called == false); init_once_called = true; } void OS::Shutdown() { } void OS::Abort() { abort(); } void OS::Exit(int code) { exit(code); } } // namespace dart #endif // defined(TARGET_OS_LINUX)