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