// Copyright 2016 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "src/perf-jit.h" #include #include "src/assembler.h" #include "src/eh-frame.h" #include "src/instruction-stream.h" #include "src/objects-inl.h" #include "src/source-position-table.h" #include "src/wasm/wasm-code-manager.h" #if V8_OS_LINUX #include #include #undef MAP_TYPE // jumbo: conflicts with v8::internal::InstanceType::MAP_TYPE #include #endif // V8_OS_LINUX namespace v8 { namespace internal { #if V8_OS_LINUX struct PerfJitHeader { uint32_t magic_; uint32_t version_; uint32_t size_; uint32_t elf_mach_target_; uint32_t reserved_; uint32_t process_id_; uint64_t time_stamp_; uint64_t flags_; static const uint32_t kMagic = 0x4A695444; static const uint32_t kVersion = 1; }; struct PerfJitBase { enum PerfJitEvent { kLoad = 0, kMove = 1, kDebugInfo = 2, kClose = 3, kUnwindingInfo = 4 }; uint32_t event_; uint32_t size_; uint64_t time_stamp_; }; struct PerfJitCodeLoad : PerfJitBase { uint32_t process_id_; uint32_t thread_id_; uint64_t vma_; uint64_t code_address_; uint64_t code_size_; uint64_t code_id_; }; struct PerfJitDebugEntry { uint64_t address_; int line_number_; int column_; // Followed by null-terminated name or \0xFF\0 if same as previous. }; struct PerfJitCodeDebugInfo : PerfJitBase { uint64_t address_; uint64_t entry_count_; // Followed by entry_count_ instances of PerfJitDebugEntry. }; struct PerfJitCodeUnwindingInfo : PerfJitBase { uint64_t unwinding_size_; uint64_t eh_frame_hdr_size_; uint64_t mapped_size_; // Followed by size_ - sizeof(PerfJitCodeUnwindingInfo) bytes of data. }; const char PerfJitLogger::kFilenameFormatString[] = "./jit-%d.dump"; // Extra padding for the PID in the filename const int PerfJitLogger::kFilenameBufferPadding = 16; base::LazyRecursiveMutex PerfJitLogger::file_mutex_; // The following static variables are protected by PerfJitLogger::file_mutex_. uint64_t PerfJitLogger::reference_count_ = 0; void* PerfJitLogger::marker_address_ = nullptr; uint64_t PerfJitLogger::code_index_ = 0; FILE* PerfJitLogger::perf_output_handle_ = nullptr; void PerfJitLogger::OpenJitDumpFile() { // Open the perf JIT dump file. perf_output_handle_ = nullptr; int bufferSize = sizeof(kFilenameFormatString) + kFilenameBufferPadding; ScopedVector perf_dump_name(bufferSize); int size = SNPrintF(perf_dump_name, kFilenameFormatString, base::OS::GetCurrentProcessId()); CHECK_NE(size, -1); int fd = open(perf_dump_name.start(), O_CREAT | O_TRUNC | O_RDWR, 0666); if (fd == -1) return; marker_address_ = OpenMarkerFile(fd); if (marker_address_ == nullptr) return; perf_output_handle_ = fdopen(fd, "w+"); if (perf_output_handle_ == nullptr) return; setvbuf(perf_output_handle_, nullptr, _IOFBF, kLogBufferSize); } void PerfJitLogger::CloseJitDumpFile() { if (perf_output_handle_ == nullptr) return; fclose(perf_output_handle_); perf_output_handle_ = nullptr; } void* PerfJitLogger::OpenMarkerFile(int fd) { long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int) if (page_size == -1) return nullptr; // Mmap the file so that there is a mmap record in the perf_data file. // // The map must be PROT_EXEC to ensure it is not ignored by perf record. void* marker_address = mmap(nullptr, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0); return (marker_address == MAP_FAILED) ? nullptr : marker_address; } void PerfJitLogger::CloseMarkerFile(void* marker_address) { if (marker_address == nullptr) return; long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int) if (page_size == -1) return; munmap(marker_address, page_size); } PerfJitLogger::PerfJitLogger(Isolate* isolate) : CodeEventLogger(isolate) { base::LockGuard guard_file(file_mutex_.Pointer()); reference_count_++; // If this is the first logger, open the file and write the header. if (reference_count_ == 1) { OpenJitDumpFile(); if (perf_output_handle_ == nullptr) return; LogWriteHeader(); } } PerfJitLogger::~PerfJitLogger() { base::LockGuard guard_file(file_mutex_.Pointer()); reference_count_--; // If this was the last logger, close the file. if (reference_count_ == 0) { CloseJitDumpFile(); } } uint64_t PerfJitLogger::GetTimestamp() { struct timespec ts; int result = clock_gettime(CLOCK_MONOTONIC, &ts); DCHECK_EQ(0, result); USE(result); static const uint64_t kNsecPerSec = 1000000000; return (ts.tv_sec * kNsecPerSec) + ts.tv_nsec; } void PerfJitLogger::LogRecordedBuffer(AbstractCode* abstract_code, SharedFunctionInfo* shared, const char* name, int length) { if (FLAG_perf_basic_prof_only_functions && (abstract_code->kind() != AbstractCode::INTERPRETED_FUNCTION && abstract_code->kind() != AbstractCode::OPTIMIZED_FUNCTION)) { return; } base::LockGuard guard_file(file_mutex_.Pointer()); if (perf_output_handle_ == nullptr) return; // We only support non-interpreted functions. if (!abstract_code->IsCode()) return; Code* code = abstract_code->GetCode(); DCHECK(code->raw_instruction_start() == code->address() + Code::kHeaderSize); // Debug info has to be emitted first. if (FLAG_perf_prof && shared != nullptr) { // TODO(herhut): This currently breaks for js2wasm/wasm2js functions. if (code->kind() != Code::JS_TO_WASM_FUNCTION && code->kind() != Code::WASM_TO_JS_FUNCTION) { LogWriteDebugInfo(code, shared); } } const char* code_name = name; uint8_t* code_pointer = reinterpret_cast(code->InstructionStart()); // Code generated by Turbofan will have the safepoint table directly after // instructions. There is no need to record the safepoint table itself. uint32_t code_size = code->is_turbofanned() ? code->safepoint_table_offset() : code->InstructionSize(); // Unwinding info comes right after debug info. if (FLAG_perf_prof_unwinding_info) LogWriteUnwindingInfo(code); WriteJitCodeLoadEntry(code_pointer, code_size, code_name, length); } void PerfJitLogger::LogRecordedBuffer(const wasm::WasmCode* code, const char* name, int length) { base::LockGuard guard_file(file_mutex_.Pointer()); if (perf_output_handle_ == nullptr) return; WriteJitCodeLoadEntry(code->instructions().start(), code->instructions().length(), name, length); } void PerfJitLogger::WriteJitCodeLoadEntry(const uint8_t* code_pointer, uint32_t code_size, const char* name, int name_length) { static const char string_terminator[] = "\0"; PerfJitCodeLoad code_load; code_load.event_ = PerfJitCodeLoad::kLoad; code_load.size_ = sizeof(code_load) + name_length + 1 + code_size; code_load.time_stamp_ = GetTimestamp(); code_load.process_id_ = static_cast(base::OS::GetCurrentProcessId()); code_load.thread_id_ = static_cast(base::OS::GetCurrentThreadId()); code_load.vma_ = reinterpret_cast(code_pointer); code_load.code_address_ = reinterpret_cast(code_pointer); code_load.code_size_ = code_size; code_load.code_id_ = code_index_; code_index_++; LogWriteBytes(reinterpret_cast(&code_load), sizeof(code_load)); LogWriteBytes(name, name_length); LogWriteBytes(string_terminator, 1); LogWriteBytes(reinterpret_cast(code_pointer), code_size); } namespace { constexpr char kUnknownScriptNameString[] = ""; constexpr size_t kUnknownScriptNameStringLen = arraysize(kUnknownScriptNameString) - 1; size_t GetScriptNameLength(const SourcePositionInfo& info) { if (!info.script.is_null()) { Object* name_or_url = info.script->GetNameOrSourceURL(); if (name_or_url->IsString()) { String* str = String::cast(name_or_url); if (str->IsOneByteRepresentation()) return str->length(); int length; str->ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length); return static_cast(length); } } return kUnknownScriptNameStringLen; } Vector GetScriptName(const SourcePositionInfo& info, std::unique_ptr* storage) { if (!info.script.is_null()) { Object* name_or_url = info.script->GetNameOrSourceURL(); if (name_or_url->IsSeqOneByteString()) { SeqOneByteString* str = SeqOneByteString::cast(name_or_url); return {reinterpret_cast(str->GetChars()), static_cast(str->length())}; } else if (name_or_url->IsString()) { int length; *storage = String::cast(name_or_url) ->ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length); return {storage->get(), static_cast(length)}; } } return {kUnknownScriptNameString, kUnknownScriptNameStringLen}; } SourcePositionInfo GetSourcePositionInfo(Handle code, Handle function, SourcePosition pos) { if (code->is_turbofanned()) { DisallowHeapAllocation disallow; return pos.InliningStack(code)[0]; } else { return SourcePositionInfo(pos, function); } } } // namespace void PerfJitLogger::LogWriteDebugInfo(Code* code, SharedFunctionInfo* shared) { // Compute the entry count and get the name of the script. uint32_t entry_count = 0; for (SourcePositionTableIterator iterator(code->SourcePositionTable()); !iterator.done(); iterator.Advance()) { entry_count++; } if (entry_count == 0) return; // The WasmToJS wrapper stubs have source position entries. if (!shared->HasSourceCode()) return; Isolate* isolate = shared->GetIsolate(); Handle