| // 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 <memory> |
| |
| #include "src/assembler.h" |
| #include "src/eh-frame.h" |
| #include "src/objects-inl.h" |
| #include "src/source-position-table.h" |
| |
| #if V8_OS_LINUX |
| #include <fcntl.h> |
| #include <sys/mman.h> |
| #undef MAP_TYPE // jumbo: conflicts with v8::internal::InstanceType::MAP_TYPE |
| #include <unistd.h> |
| #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<char> 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() { |
| base::LockGuard<base::RecursiveMutex> 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<base::RecursiveMutex> 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<base::RecursiveMutex> 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->instruction_start() == code->address() + Code::kHeaderSize); |
| |
| // Debug info has to be emitted first. |
| if (FLAG_perf_prof && shared != nullptr) { |
| LogWriteDebugInfo(code, shared); |
| } |
| |
| const char* code_name = name; |
| uint8_t* code_pointer = reinterpret_cast<uint8_t*>(code->instruction_start()); |
| // 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->instruction_size(); |
| |
| // Unwinding info comes right after debug info. |
| if (FLAG_perf_prof_unwinding_info) LogWriteUnwindingInfo(code); |
| |
| static const char string_terminator[] = "\0"; |
| |
| PerfJitCodeLoad code_load; |
| code_load.event_ = PerfJitCodeLoad::kLoad; |
| code_load.size_ = sizeof(code_load) + length + 1 + code_size; |
| code_load.time_stamp_ = GetTimestamp(); |
| code_load.process_id_ = |
| static_cast<uint32_t>(base::OS::GetCurrentProcessId()); |
| code_load.thread_id_ = static_cast<uint32_t>(base::OS::GetCurrentThreadId()); |
| code_load.vma_ = 0x0; // Our addresses are absolute. |
| code_load.code_address_ = reinterpret_cast<uint64_t>(code_pointer); |
| code_load.code_size_ = code_size; |
| code_load.code_id_ = code_index_; |
| |
| code_index_++; |
| |
| LogWriteBytes(reinterpret_cast<const char*>(&code_load), sizeof(code_load)); |
| LogWriteBytes(code_name, length); |
| LogWriteBytes(string_terminator, 1); |
| LogWriteBytes(reinterpret_cast<const char*>(code_pointer), code_size); |
| } |
| |
| namespace { |
| |
| std::unique_ptr<char[]> GetScriptName(Handle<Script> script) { |
| Object* name_or_url = script->GetNameOrSourceURL(); |
| int name_length = 0; |
| std::unique_ptr<char[]> name_string; |
| if (name_or_url->IsString()) { |
| return String::cast(name_or_url) |
| ->ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &name_length); |
| } else { |
| const char unknown[] = "<unknown>"; |
| name_length = static_cast<int>(strlen(unknown)); |
| char* buffer = NewArray<char>(name_length); |
| base::OS::StrNCpy(buffer, name_length + 1, unknown, |
| static_cast<size_t>(name_length)); |
| return std::unique_ptr<char[]>(buffer); |
| } |
| } |
| |
| SourcePositionInfo GetSourcePositionInfo(Handle<Code> code, |
| Handle<SharedFunctionInfo> 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; |
| Handle<Script> script(Script::cast(shared->script())); |
| |
| PerfJitCodeDebugInfo debug_info; |
| |
| debug_info.event_ = PerfJitCodeLoad::kDebugInfo; |
| debug_info.time_stamp_ = GetTimestamp(); |
| debug_info.address_ = reinterpret_cast<uint64_t>(code->instruction_start()); |
| debug_info.entry_count_ = entry_count; |
| |
| uint32_t size = sizeof(debug_info); |
| // Add the sizes of fixed parts of entries. |
| size += entry_count * sizeof(PerfJitDebugEntry); |
| // Add the size of the name after each entry. |
| |
| Handle<Code> code_handle(code); |
| Handle<SharedFunctionInfo> function_handle(shared); |
| for (SourcePositionTableIterator iterator(code->SourcePositionTable()); |
| !iterator.done(); iterator.Advance()) { |
| SourcePositionInfo info(GetSourcePositionInfo(code_handle, function_handle, |
| iterator.source_position())); |
| Handle<Script> script(Script::cast(info.function->script())); |
| std::unique_ptr<char[]> name_string = GetScriptName(script); |
| size += (static_cast<uint32_t>(strlen(name_string.get())) + 1); |
| } |
| |
| int padding = ((size + 7) & (~7)) - size; |
| debug_info.size_ = size + padding; |
| LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info)); |
| |
| Address code_start = code->instruction_start(); |
| |
| for (SourcePositionTableIterator iterator(code->SourcePositionTable()); |
| !iterator.done(); iterator.Advance()) { |
| SourcePositionInfo info(GetSourcePositionInfo(code_handle, function_handle, |
| iterator.source_position())); |
| PerfJitDebugEntry entry; |
| // The entry point of the function will be placed straight after the ELF |
| // header when processed by "perf inject". Adjust the position addresses |
| // accordingly. |
| entry.address_ = reinterpret_cast<intptr_t>( |
| code_start + iterator.code_offset() + kElfHeaderSize); |
| entry.line_number_ = info.line + 1; |
| entry.column_ = info.column + 1; |
| LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry)); |
| Handle<Script> script(Script::cast(info.function->script())); |
| std::unique_ptr<char[]> name_string = GetScriptName(script); |
| LogWriteBytes(name_string.get(), |
| static_cast<uint32_t>(strlen(name_string.get())) + 1); |
| } |
| char padding_bytes[] = "\0\0\0\0\0\0\0\0"; |
| LogWriteBytes(padding_bytes, padding); |
| } |
| |
| void PerfJitLogger::LogWriteUnwindingInfo(Code* code) { |
| PerfJitCodeUnwindingInfo unwinding_info_header; |
| unwinding_info_header.event_ = PerfJitCodeLoad::kUnwindingInfo; |
| unwinding_info_header.time_stamp_ = GetTimestamp(); |
| unwinding_info_header.eh_frame_hdr_size_ = EhFrameConstants::kEhFrameHdrSize; |
| |
| if (code->has_unwinding_info()) { |
| unwinding_info_header.unwinding_size_ = code->unwinding_info_size(); |
| unwinding_info_header.mapped_size_ = unwinding_info_header.unwinding_size_; |
| } else { |
| unwinding_info_header.unwinding_size_ = EhFrameConstants::kEhFrameHdrSize; |
| unwinding_info_header.mapped_size_ = 0; |
| } |
| |
| int content_size = static_cast<int>(sizeof(unwinding_info_header) + |
| unwinding_info_header.unwinding_size_); |
| int padding_size = RoundUp(content_size, 8) - content_size; |
| unwinding_info_header.size_ = content_size + padding_size; |
| |
| LogWriteBytes(reinterpret_cast<const char*>(&unwinding_info_header), |
| sizeof(unwinding_info_header)); |
| |
| if (code->has_unwinding_info()) { |
| LogWriteBytes(reinterpret_cast<const char*>(code->unwinding_info_start()), |
| code->unwinding_info_size()); |
| } else { |
| OFStream perf_output_stream(perf_output_handle_); |
| EhFrameWriter::WriteEmptyEhFrame(perf_output_stream); |
| } |
| |
| char padding_bytes[] = "\0\0\0\0\0\0\0\0"; |
| DCHECK_LT(padding_size, static_cast<int>(sizeof(padding_bytes))); |
| LogWriteBytes(padding_bytes, static_cast<int>(padding_size)); |
| } |
| |
| void PerfJitLogger::CodeMoveEvent(AbstractCode* from, Address to) { |
| // We may receive a CodeMove event if a BytecodeArray object moves. Otherwise |
| // code relocation is not supported. |
| CHECK(from->IsBytecodeArray()); |
| } |
| |
| void PerfJitLogger::LogWriteBytes(const char* bytes, int size) { |
| size_t rv = fwrite(bytes, 1, size, perf_output_handle_); |
| DCHECK(static_cast<size_t>(size) == rv); |
| USE(rv); |
| } |
| |
| void PerfJitLogger::LogWriteHeader() { |
| DCHECK_NOT_NULL(perf_output_handle_); |
| PerfJitHeader header; |
| |
| header.magic_ = PerfJitHeader::kMagic; |
| header.version_ = PerfJitHeader::kVersion; |
| header.size_ = sizeof(header); |
| header.elf_mach_target_ = GetElfMach(); |
| header.reserved_ = 0xDEADBEEF; |
| header.process_id_ = base::OS::GetCurrentProcessId(); |
| header.time_stamp_ = |
| static_cast<uint64_t>(V8::GetCurrentPlatform()->CurrentClockTimeMillis() * |
| base::Time::kMicrosecondsPerMillisecond); |
| header.flags_ = 0; |
| |
| LogWriteBytes(reinterpret_cast<const char*>(&header), sizeof(header)); |
| } |
| |
| #endif // V8_OS_LINUX |
| } // namespace internal |
| } // namespace v8 |