1// Copyright 2016 the V8 project authors. All rights reserved. 2// Redistribution and use in source and binary forms, with or without 3// modification, are permitted provided that the following conditions are 4// met: 5// 6// * Redistributions of source code must retain the above copyright 7// notice, this list of conditions and the following disclaimer. 8// * Redistributions in binary form must reproduce the above 9// copyright notice, this list of conditions and the following 10// disclaimer in the documentation and/or other materials provided 11// with the distribution. 12// * Neither the name of Google Inc. nor the names of its 13// contributors may be used to endorse or promote products derived 14// from this software without specific prior written permission. 15// 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28#include "src/diagnostics/perf-jit.h" 29 30#include "src/common/assert-scope.h" 31 32// Only compile the {PerfJitLogger} on Linux. 33#if V8_OS_LINUX 34 35#include <fcntl.h> 36#include <sys/mman.h> 37#include <unistd.h> 38 39#include <memory> 40 41#include "src/base/platform/wrappers.h" 42#include "src/codegen/assembler.h" 43#include "src/codegen/source-position-table.h" 44#include "src/diagnostics/eh-frame.h" 45#include "src/objects/code-kind.h" 46#include "src/objects/objects-inl.h" 47#include "src/objects/shared-function-info.h" 48#include "src/snapshot/embedded/embedded-data.h" 49#include "src/utils/ostreams.h" 50 51#if V8_ENABLE_WEBASSEMBLY 52#include "src/wasm/wasm-code-manager.h" 53#endif // V8_ENABLE_WEBASSEMBLY 54 55namespace v8 { 56namespace internal { 57 58struct PerfJitHeader { 59 uint32_t magic_; 60 uint32_t version_; 61 uint32_t size_; 62 uint32_t elf_mach_target_; 63 uint32_t reserved_; 64 uint32_t process_id_; 65 uint64_t time_stamp_; 66 uint64_t flags_; 67 68 static const uint32_t kMagic = 0x4A695444; 69 static const uint32_t kVersion = 1; 70}; 71 72struct PerfJitBase { 73 enum PerfJitEvent { 74 kLoad = 0, 75 kMove = 1, 76 kDebugInfo = 2, 77 kClose = 3, 78 kUnwindingInfo = 4 79 }; 80 81 uint32_t event_; 82 uint32_t size_; 83 uint64_t time_stamp_; 84}; 85 86struct PerfJitCodeLoad : PerfJitBase { 87 uint32_t process_id_; 88 uint32_t thread_id_; 89 uint64_t vma_; 90 uint64_t code_address_; 91 uint64_t code_size_; 92 uint64_t code_id_; 93}; 94 95struct PerfJitDebugEntry { 96 uint64_t address_; 97 int line_number_; 98 int column_; 99 // Followed by null-terminated name or \0xFF\0 if same as previous. 100}; 101 102struct PerfJitCodeDebugInfo : PerfJitBase { 103 uint64_t address_; 104 uint64_t entry_count_; 105 // Followed by entry_count_ instances of PerfJitDebugEntry. 106}; 107 108struct PerfJitCodeUnwindingInfo : PerfJitBase { 109 uint64_t unwinding_size_; 110 uint64_t eh_frame_hdr_size_; 111 uint64_t mapped_size_; 112 // Followed by size_ - sizeof(PerfJitCodeUnwindingInfo) bytes of data. 113}; 114 115const char PerfJitLogger::kFilenameFormatString[] = "./jit-%d.dump"; 116 117// Extra padding for the PID in the filename 118const int PerfJitLogger::kFilenameBufferPadding = 16; 119 120static const char kStringTerminator[] = {'\0'}; 121static const char kRepeatedNameMarker[] = {'\xff', '\0'}; 122 123base::LazyRecursiveMutex PerfJitLogger::file_mutex_; 124// The following static variables are protected by PerfJitLogger::file_mutex_. 125int PerfJitLogger::process_id_ = 0; 126uint64_t PerfJitLogger::reference_count_ = 0; 127void* PerfJitLogger::marker_address_ = nullptr; 128uint64_t PerfJitLogger::code_index_ = 0; 129FILE* PerfJitLogger::perf_output_handle_ = nullptr; 130 131void PerfJitLogger::OpenJitDumpFile() { 132 // Open the perf JIT dump file. 133 perf_output_handle_ = nullptr; 134 135 int bufferSize = sizeof(kFilenameFormatString) + kFilenameBufferPadding; 136 base::ScopedVector<char> perf_dump_name(bufferSize); 137 int size = SNPrintF(perf_dump_name, kFilenameFormatString, process_id_); 138 CHECK_NE(size, -1); 139 140 int fd = open(perf_dump_name.begin(), O_CREAT | O_TRUNC | O_RDWR, 0666); 141 if (fd == -1) return; 142 143 // If --perf-prof-delete-file is given, unlink the file right after opening 144 // it. This keeps the file handle to the file valid. This only works on Linux, 145 // which is the only platform supported for --perf-prof anyway. 146 if (FLAG_perf_prof_delete_file) CHECK_EQ(0, unlink(perf_dump_name.begin())); 147 148 marker_address_ = OpenMarkerFile(fd); 149 if (marker_address_ == nullptr) return; 150 151 perf_output_handle_ = fdopen(fd, "w+"); 152 if (perf_output_handle_ == nullptr) return; 153 154 setvbuf(perf_output_handle_, nullptr, _IOFBF, kLogBufferSize); 155} 156 157void PerfJitLogger::CloseJitDumpFile() { 158 if (perf_output_handle_ == nullptr) return; 159 base::Fclose(perf_output_handle_); 160 perf_output_handle_ = nullptr; 161} 162 163void* PerfJitLogger::OpenMarkerFile(int fd) { 164 long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int) 165 if (page_size == -1) return nullptr; 166 167 // Mmap the file so that there is a mmap record in the perf_data file. 168 // 169 // The map must be PROT_EXEC to ensure it is not ignored by perf record. 170 void* marker_address = 171 mmap(nullptr, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0); 172 return (marker_address == MAP_FAILED) ? nullptr : marker_address; 173} 174 175void PerfJitLogger::CloseMarkerFile(void* marker_address) { 176 if (marker_address == nullptr) return; 177 long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int) 178 if (page_size == -1) return; 179 munmap(marker_address, page_size); 180} 181 182PerfJitLogger::PerfJitLogger(Isolate* isolate) : CodeEventLogger(isolate) { 183 base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer()); 184 process_id_ = base::OS::GetCurrentProcessId(); 185 186 reference_count_++; 187 // If this is the first logger, open the file and write the header. 188 if (reference_count_ == 1) { 189 OpenJitDumpFile(); 190 if (perf_output_handle_ == nullptr) return; 191 LogWriteHeader(); 192 } 193} 194 195PerfJitLogger::~PerfJitLogger() { 196 base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer()); 197 198 reference_count_--; 199 // If this was the last logger, close the file. 200 if (reference_count_ == 0) { 201 CloseJitDumpFile(); 202 } 203} 204 205uint64_t PerfJitLogger::GetTimestamp() { 206 struct timespec ts; 207 int result = clock_gettime(CLOCK_MONOTONIC, &ts); 208 DCHECK_EQ(0, result); 209 USE(result); 210 static const uint64_t kNsecPerSec = 1000000000; 211 return (ts.tv_sec * kNsecPerSec) + ts.tv_nsec; 212} 213 214void PerfJitLogger::LogRecordedBuffer( 215 Handle<AbstractCode> abstract_code, 216 MaybeHandle<SharedFunctionInfo> maybe_shared, const char* name, 217 int length) { 218 if (FLAG_perf_basic_prof_only_functions && !CodeKindIsJSFunction(abstract_code->kind())) { 219 return; 220 } 221 222 base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer()); 223 224 if (perf_output_handle_ == nullptr) return; 225 226 // We only support non-interpreted functions. 227 if (!abstract_code->IsCode()) return; 228 Handle<Code> code = Handle<Code>::cast(abstract_code); 229 DCHECK(code->raw_instruction_start() == code->address() + Code::kHeaderSize); 230 231 // Debug info has to be emitted first. 232 Handle<SharedFunctionInfo> shared; 233 if (FLAG_perf_prof && maybe_shared.ToHandle(&shared)) { 234 // TODO(herhut): This currently breaks for js2wasm/wasm2js functions. 235 if (code->kind() != CodeKind::JS_TO_WASM_FUNCTION && 236 code->kind() != CodeKind::WASM_TO_JS_FUNCTION) { 237 LogWriteDebugInfo(code, shared); 238 } 239 } 240 241 const char* code_name = name; 242 uint8_t* code_pointer = reinterpret_cast<uint8_t*>(code->InstructionStart()); 243 244 // Unwinding info comes right after debug info. 245 if (FLAG_perf_prof_unwinding_info) LogWriteUnwindingInfo(*code); 246 247 WriteJitCodeLoadEntry(code_pointer, code->InstructionSize(), code_name, 248 length); 249} 250 251#if V8_ENABLE_WEBASSEMBLY 252void PerfJitLogger::LogRecordedBuffer(const wasm::WasmCode* code, 253 const char* name, int length) { 254 base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer()); 255 256 if (perf_output_handle_ == nullptr) return; 257 258 if (FLAG_perf_prof_annotate_wasm) LogWriteDebugInfo(code); 259 260 WriteJitCodeLoadEntry(code->instructions().begin(), 261 code->instructions().length(), name, length); 262} 263#endif // V8_ENABLE_WEBASSEMBLY 264 265void PerfJitLogger::WriteJitCodeLoadEntry(const uint8_t* code_pointer, 266 uint32_t code_size, const char* name, 267 int name_length) { 268 PerfJitCodeLoad code_load; 269 code_load.event_ = PerfJitCodeLoad::kLoad; 270 code_load.size_ = sizeof(code_load) + name_length + 1 + code_size; 271 code_load.time_stamp_ = GetTimestamp(); 272 code_load.process_id_ = static_cast<uint32_t>(process_id_); 273 code_load.thread_id_ = static_cast<uint32_t>(base::OS::GetCurrentThreadId()); 274 code_load.vma_ = reinterpret_cast<uint64_t>(code_pointer); 275 code_load.code_address_ = reinterpret_cast<uint64_t>(code_pointer); 276 code_load.code_size_ = code_size; 277 code_load.code_id_ = code_index_; 278 279 code_index_++; 280 281 LogWriteBytes(reinterpret_cast<const char*>(&code_load), sizeof(code_load)); 282 LogWriteBytes(name, name_length); 283 LogWriteBytes(kStringTerminator, sizeof(kStringTerminator)); 284 LogWriteBytes(reinterpret_cast<const char*>(code_pointer), code_size); 285} 286 287namespace { 288 289constexpr char kUnknownScriptNameString[] = "<unknown>"; 290constexpr size_t kUnknownScriptNameStringLen = 291 arraysize(kUnknownScriptNameString) - 1; 292 293namespace { 294base::Vector<const char> GetScriptName(Object maybeScript, 295 std::unique_ptr<char[]>* storage, 296 const DisallowGarbageCollection& no_gc) { 297 if (maybeScript.IsScript()) { 298 Object name_or_url = Script::cast(maybeScript).GetNameOrSourceURL(); 299 if (name_or_url.IsSeqOneByteString()) { 300 SeqOneByteString str = SeqOneByteString::cast(name_or_url); 301 return {reinterpret_cast<char*>(str.GetChars(no_gc)), 302 static_cast<size_t>(str.length())}; 303 } else if (name_or_url.IsString()) { 304 int length; 305 *storage = String::cast(name_or_url) 306 .ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length); 307 return {storage->get(), static_cast<size_t>(length)}; 308 } 309 } 310 return {kUnknownScriptNameString, kUnknownScriptNameStringLen}; 311} 312 313} // namespace 314 315SourcePositionInfo GetSourcePositionInfo(Handle<Code> code, 316 Handle<SharedFunctionInfo> function, 317 SourcePosition pos) { 318 DisallowGarbageCollection disallow; 319 if (code->is_turbofanned()) { 320 return pos.FirstInfo(code); 321 } else { 322 return SourcePositionInfo(pos, function); 323 } 324} 325 326} // namespace 327 328void PerfJitLogger::LogWriteDebugInfo(Handle<Code> code, 329 Handle<SharedFunctionInfo> shared) { 330 // Line ends of all scripts have been initialized prior to this. 331 DisallowGarbageCollection no_gc; 332 // The WasmToJS wrapper stubs have source position entries. 333 if (!shared->HasSourceCode()) return; 334 335 PerfJitCodeDebugInfo debug_info; 336 uint32_t size = sizeof(debug_info); 337 338 ByteArray source_position_table = code->SourcePositionTable(*shared); 339 // Compute the entry count and get the names of all scripts. 340 // Avoid additional work if the script name is repeated. Multiple script 341 // names only occur for cross-script inlining. 342 uint32_t entry_count = 0; 343 Object last_script = Smi::zero(); 344 std::vector<base::Vector<const char>> script_names; 345 for (SourcePositionTableIterator iterator(source_position_table); 346 !iterator.done(); iterator.Advance()) { 347 SourcePositionInfo info( 348 GetSourcePositionInfo(code, shared, iterator.source_position())); 349 Object current_script = *info.script; 350 if (current_script != last_script) { 351 std::unique_ptr<char[]> name_storage; 352 auto name = GetScriptName(shared->script(), &name_storage, no_gc); 353 script_names.push_back(name); 354 // Add the size of the name after each entry. 355 size += name.size() + sizeof(kStringTerminator); 356 last_script = current_script; 357 } else { 358 size += sizeof(kRepeatedNameMarker); 359 } 360 entry_count++; 361 } 362 if (entry_count == 0) return; 363 364 debug_info.event_ = PerfJitCodeLoad::kDebugInfo; 365 debug_info.time_stamp_ = GetTimestamp(); 366 debug_info.address_ = code->InstructionStart(); 367 debug_info.entry_count_ = entry_count; 368 369 // Add the sizes of fixed parts of entries. 370 size += entry_count * sizeof(PerfJitDebugEntry); 371 372 int padding = ((size + 7) & (~7)) - size; 373 debug_info.size_ = size + padding; 374 LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info)); 375 376 Address code_start = code->InstructionStart(); 377 378 last_script = Smi::zero(); 379 int script_names_index = 0; 380 for (SourcePositionTableIterator iterator(source_position_table); 381 !iterator.done(); iterator.Advance()) { 382 SourcePositionInfo info( 383 GetSourcePositionInfo(code, shared, iterator.source_position())); 384 PerfJitDebugEntry entry; 385 // The entry point of the function will be placed straight after the ELF 386 // header when processed by "perf inject". Adjust the position addresses 387 // accordingly. 388 entry.address_ = code_start + iterator.code_offset() + kElfHeaderSize; 389 entry.line_number_ = info.line + 1; 390 entry.column_ = info.column + 1; 391 LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry)); 392 Object current_script = *info.script; 393 if (current_script != last_script) { 394 auto name_string = script_names[script_names_index]; 395 LogWriteBytes(name_string.begin(), 396 static_cast<uint32_t>(name_string.size())); 397 LogWriteBytes(kStringTerminator, sizeof(kStringTerminator)); 398 script_names_index++; 399 last_script = current_script; 400 } else { 401 // Use the much shorter kRepeatedNameMarker for repeated names. 402 LogWriteBytes(kRepeatedNameMarker, sizeof(kRepeatedNameMarker)); 403 } 404 } 405 char padding_bytes[8] = {0}; 406 LogWriteBytes(padding_bytes, padding); 407} 408 409#if V8_ENABLE_WEBASSEMBLY 410void PerfJitLogger::LogWriteDebugInfo(const wasm::WasmCode* code) { 411 wasm::WasmModuleSourceMap* source_map = 412 code->native_module()->GetWasmSourceMap(); 413 wasm::WireBytesRef code_ref = 414 code->native_module()->module()->functions[code->index()].code; 415 uint32_t code_offset = code_ref.offset(); 416 uint32_t code_end_offset = code_ref.end_offset(); 417 418 uint32_t entry_count = 0; 419 uint32_t size = 0; 420 421 if (!source_map || !source_map->IsValid() || 422 !source_map->HasSource(code_offset, code_end_offset)) { 423 return; 424 } 425 426 for (SourcePositionTableIterator iterator(code->source_positions()); 427 !iterator.done(); iterator.Advance()) { 428 uint32_t offset = iterator.source_position().ScriptOffset() + code_offset; 429 if (!source_map->HasValidEntry(code_offset, offset)) continue; 430 entry_count++; 431 size += source_map->GetFilename(offset).size() + 1; 432 } 433 434 if (entry_count == 0) return; 435 436 PerfJitCodeDebugInfo debug_info; 437 438 debug_info.event_ = PerfJitCodeLoad::kDebugInfo; 439 debug_info.time_stamp_ = GetTimestamp(); 440 debug_info.address_ = 441 reinterpret_cast<uintptr_t>(code->instructions().begin()); 442 debug_info.entry_count_ = entry_count; 443 444 size += sizeof(debug_info); 445 // Add the sizes of fixed parts of entries. 446 size += entry_count * sizeof(PerfJitDebugEntry); 447 448 int padding = ((size + 7) & (~7)) - size; 449 debug_info.size_ = size + padding; 450 LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info)); 451 452 uintptr_t code_begin = 453 reinterpret_cast<uintptr_t>(code->instructions().begin()); 454 455 for (SourcePositionTableIterator iterator(code->source_positions()); 456 !iterator.done(); iterator.Advance()) { 457 uint32_t offset = iterator.source_position().ScriptOffset() + code_offset; 458 if (!source_map->HasValidEntry(code_offset, offset)) continue; 459 PerfJitDebugEntry entry; 460 // The entry point of the function will be placed straight after the ELF 461 // header when processed by "perf inject". Adjust the position addresses 462 // accordingly. 463 entry.address_ = code_begin + iterator.code_offset() + kElfHeaderSize; 464 entry.line_number_ = 465 static_cast<int>(source_map->GetSourceLine(offset)) + 1; 466 entry.column_ = 1; 467 LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry)); 468 std::string name_string = source_map->GetFilename(offset); 469 LogWriteBytes(name_string.c_str(), static_cast<int>(name_string.size())); 470 LogWriteBytes(kStringTerminator, sizeof(kStringTerminator)); 471 } 472 473 char padding_bytes[8] = {0}; 474 LogWriteBytes(padding_bytes, padding); 475} 476#endif // V8_ENABLE_WEBASSEMBLY 477 478void PerfJitLogger::LogWriteUnwindingInfo(Code code) { 479 PerfJitCodeUnwindingInfo unwinding_info_header; 480 unwinding_info_header.event_ = PerfJitCodeLoad::kUnwindingInfo; 481 unwinding_info_header.time_stamp_ = GetTimestamp(); 482 unwinding_info_header.eh_frame_hdr_size_ = EhFrameConstants::kEhFrameHdrSize; 483 484 if (code.has_unwinding_info()) { 485 unwinding_info_header.unwinding_size_ = code.unwinding_info_size(); 486 unwinding_info_header.mapped_size_ = unwinding_info_header.unwinding_size_; 487 } else { 488 unwinding_info_header.unwinding_size_ = EhFrameConstants::kEhFrameHdrSize; 489 unwinding_info_header.mapped_size_ = 0; 490 } 491 492 int content_size = static_cast<int>(sizeof(unwinding_info_header) + 493 unwinding_info_header.unwinding_size_); 494 int padding_size = RoundUp(content_size, 8) - content_size; 495 unwinding_info_header.size_ = content_size + padding_size; 496 497 LogWriteBytes(reinterpret_cast<const char*>(&unwinding_info_header), 498 sizeof(unwinding_info_header)); 499 500 if (code.has_unwinding_info()) { 501 LogWriteBytes(reinterpret_cast<const char*>(code.unwinding_info_start()), 502 code.unwinding_info_size()); 503 } else { 504 OFStream perf_output_stream(perf_output_handle_); 505 EhFrameWriter::WriteEmptyEhFrame(perf_output_stream); 506 } 507 508 char padding_bytes[] = "\0\0\0\0\0\0\0\0"; 509 DCHECK_LT(padding_size, static_cast<int>(sizeof(padding_bytes))); 510 LogWriteBytes(padding_bytes, static_cast<int>(padding_size)); 511} 512 513void PerfJitLogger::CodeMoveEvent(AbstractCode from, AbstractCode to) { 514 // We may receive a CodeMove event if a BytecodeArray object moves. Otherwise 515 // code relocation is not supported. 516 CHECK(from.IsBytecodeArray()); 517} 518 519void PerfJitLogger::LogWriteBytes(const char* bytes, int size) { 520 size_t rv = fwrite(bytes, 1, size, perf_output_handle_); 521 DCHECK(static_cast<size_t>(size) == rv); 522 USE(rv); 523} 524 525void PerfJitLogger::LogWriteHeader() { 526 DCHECK_NOT_NULL(perf_output_handle_); 527 PerfJitHeader header; 528 529 header.magic_ = PerfJitHeader::kMagic; 530 header.version_ = PerfJitHeader::kVersion; 531 header.size_ = sizeof(header); 532 header.elf_mach_target_ = GetElfMach(); 533 header.reserved_ = 0xDEADBEEF; 534 header.process_id_ = process_id_; 535 header.time_stamp_ = 536 static_cast<uint64_t>(V8::GetCurrentPlatform()->CurrentClockTimeMillis() * 537 base::Time::kMicrosecondsPerMillisecond); 538 header.flags_ = 0; 539 540 LogWriteBytes(reinterpret_cast<const char*>(&header), sizeof(header)); 541} 542 543} // namespace internal 544} // namespace v8 545 546#endif // V8_OS_LINUX 547