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/perf-jit.h"
29
30#include <memory>
31
32#include "src/assembler.h"
33#include "src/eh-frame.h"
34#include "src/objects-inl.h"
35#include "src/source-position-table.h"
36
37#if V8_OS_LINUX
38#include <fcntl.h>
39#include <sys/mman.h>
40#include <unistd.h>
41#endif  // V8_OS_LINUX
42
43namespace v8 {
44namespace internal {
45
46#if V8_OS_LINUX
47
48struct PerfJitHeader {
49  uint32_t magic_;
50  uint32_t version_;
51  uint32_t size_;
52  uint32_t elf_mach_target_;
53  uint32_t reserved_;
54  uint32_t process_id_;
55  uint64_t time_stamp_;
56  uint64_t flags_;
57
58  static const uint32_t kMagic = 0x4A695444;
59  static const uint32_t kVersion = 1;
60};
61
62struct PerfJitBase {
63  enum PerfJitEvent {
64    kLoad = 0,
65    kMove = 1,
66    kDebugInfo = 2,
67    kClose = 3,
68    kUnwindingInfo = 4
69  };
70
71  uint32_t event_;
72  uint32_t size_;
73  uint64_t time_stamp_;
74};
75
76struct PerfJitCodeLoad : PerfJitBase {
77  uint32_t process_id_;
78  uint32_t thread_id_;
79  uint64_t vma_;
80  uint64_t code_address_;
81  uint64_t code_size_;
82  uint64_t code_id_;
83};
84
85struct PerfJitDebugEntry {
86  uint64_t address_;
87  int line_number_;
88  int column_;
89  // Followed by null-terminated name or \0xff\0 if same as previous.
90};
91
92struct PerfJitCodeDebugInfo : PerfJitBase {
93  uint64_t address_;
94  uint64_t entry_count_;
95  // Followed by entry_count_ instances of PerfJitDebugEntry.
96};
97
98struct PerfJitCodeUnwindingInfo : PerfJitBase {
99  uint64_t unwinding_size_;
100  uint64_t eh_frame_hdr_size_;
101  uint64_t mapped_size_;
102  // Followed by size_ - sizeof(PerfJitCodeUnwindingInfo) bytes of data.
103};
104
105const char PerfJitLogger::kFilenameFormatString[] = "./jit-%d.dump";
106
107// Extra padding for the PID in the filename
108const int PerfJitLogger::kFilenameBufferPadding = 16;
109
110base::LazyRecursiveMutex PerfJitLogger::file_mutex_;
111// The following static variables are protected by PerfJitLogger::file_mutex_.
112uint64_t PerfJitLogger::reference_count_ = 0;
113void* PerfJitLogger::marker_address_ = nullptr;
114uint64_t PerfJitLogger::code_index_ = 0;
115FILE* PerfJitLogger::perf_output_handle_ = nullptr;
116
117void PerfJitLogger::OpenJitDumpFile() {
118  // Open the perf JIT dump file.
119  perf_output_handle_ = nullptr;
120
121  int bufferSize = sizeof(kFilenameFormatString) + kFilenameBufferPadding;
122  ScopedVector<char> perf_dump_name(bufferSize);
123  int size = SNPrintF(perf_dump_name, kFilenameFormatString,
124                      base::OS::GetCurrentProcessId());
125  CHECK_NE(size, -1);
126
127  int fd = open(perf_dump_name.start(), O_CREAT | O_TRUNC | O_RDWR, 0666);
128  if (fd == -1) return;
129
130  marker_address_ = OpenMarkerFile(fd);
131  if (marker_address_ == nullptr) return;
132
133  perf_output_handle_ = fdopen(fd, "w+");
134  if (perf_output_handle_ == nullptr) return;
135
136  setvbuf(perf_output_handle_, NULL, _IOFBF, kLogBufferSize);
137}
138
139void PerfJitLogger::CloseJitDumpFile() {
140  if (perf_output_handle_ == nullptr) return;
141  fclose(perf_output_handle_);
142  perf_output_handle_ = nullptr;
143}
144
145void* PerfJitLogger::OpenMarkerFile(int fd) {
146  long page_size = sysconf(_SC_PAGESIZE);  // NOLINT(runtime/int)
147  if (page_size == -1) return nullptr;
148
149  // Mmap the file so that there is a mmap record in the perf_data file.
150  //
151  // The map must be PROT_EXEC to ensure it is not ignored by perf record.
152  void* marker_address =
153      mmap(nullptr, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
154  return (marker_address == MAP_FAILED) ? nullptr : marker_address;
155}
156
157void PerfJitLogger::CloseMarkerFile(void* marker_address) {
158  if (marker_address == nullptr) return;
159  long page_size = sysconf(_SC_PAGESIZE);  // NOLINT(runtime/int)
160  if (page_size == -1) return;
161  munmap(marker_address, page_size);
162}
163
164PerfJitLogger::PerfJitLogger() {
165  base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
166
167  reference_count_++;
168  // If this is the first logger, open the file and write the header.
169  if (reference_count_ == 1) {
170    OpenJitDumpFile();
171    if (perf_output_handle_ == nullptr) return;
172    LogWriteHeader();
173  }
174}
175
176PerfJitLogger::~PerfJitLogger() {
177  base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
178
179  reference_count_--;
180  // If this was the last logger, close the file.
181  if (reference_count_ == 0) {
182    CloseJitDumpFile();
183  }
184}
185
186uint64_t PerfJitLogger::GetTimestamp() {
187  struct timespec ts;
188  int result = clock_gettime(CLOCK_MONOTONIC, &ts);
189  DCHECK_EQ(0, result);
190  USE(result);
191  static const uint64_t kNsecPerSec = 1000000000;
192  return (ts.tv_sec * kNsecPerSec) + ts.tv_nsec;
193}
194
195void PerfJitLogger::LogRecordedBuffer(AbstractCode* abstract_code,
196                                      SharedFunctionInfo* shared,
197                                      const char* name, int length) {
198  if (FLAG_perf_basic_prof_only_functions &&
199      (abstract_code->kind() != AbstractCode::FUNCTION &&
200       abstract_code->kind() != AbstractCode::INTERPRETED_FUNCTION &&
201       abstract_code->kind() != AbstractCode::OPTIMIZED_FUNCTION)) {
202    return;
203  }
204
205  base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
206
207  if (perf_output_handle_ == nullptr) return;
208
209  // We only support non-interpreted functions.
210  if (!abstract_code->IsCode()) return;
211  Code* code = abstract_code->GetCode();
212  DCHECK(code->instruction_start() == code->address() + Code::kHeaderSize);
213
214  // Debug info has to be emitted first.
215  if (FLAG_perf_prof && shared != nullptr) {
216    LogWriteDebugInfo(code, shared);
217  }
218
219  const char* code_name = name;
220  uint8_t* code_pointer = reinterpret_cast<uint8_t*>(code->instruction_start());
221  uint32_t code_size = code->is_crankshafted() ? code->safepoint_table_offset()
222                                               : code->instruction_size();
223
224  // Unwinding info comes right after debug info.
225  if (FLAG_perf_prof_unwinding_info) LogWriteUnwindingInfo(code);
226
227  static const char string_terminator[] = "\0";
228
229  PerfJitCodeLoad code_load;
230  code_load.event_ = PerfJitCodeLoad::kLoad;
231  code_load.size_ = sizeof(code_load) + length + 1 + code_size;
232  code_load.time_stamp_ = GetTimestamp();
233  code_load.process_id_ =
234      static_cast<uint32_t>(base::OS::GetCurrentProcessId());
235  code_load.thread_id_ = static_cast<uint32_t>(base::OS::GetCurrentThreadId());
236  code_load.vma_ = 0x0;  //  Our addresses are absolute.
237  code_load.code_address_ = reinterpret_cast<uint64_t>(code_pointer);
238  code_load.code_size_ = code_size;
239  code_load.code_id_ = code_index_;
240
241  code_index_++;
242
243  LogWriteBytes(reinterpret_cast<const char*>(&code_load), sizeof(code_load));
244  LogWriteBytes(code_name, length);
245  LogWriteBytes(string_terminator, 1);
246  LogWriteBytes(reinterpret_cast<const char*>(code_pointer), code_size);
247}
248
249namespace {
250
251std::unique_ptr<char[]> GetScriptName(Handle<Script> script) {
252  Object* name_or_url = script->GetNameOrSourceURL();
253  int name_length = 0;
254  std::unique_ptr<char[]> name_string;
255  if (name_or_url->IsString()) {
256    return String::cast(name_or_url)
257        ->ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &name_length);
258  } else {
259    const char unknown[] = "<unknown>";
260    name_length = static_cast<int>(strlen(unknown));
261    char* buffer = NewArray<char>(name_length);
262    base::OS::StrNCpy(buffer, name_length + 1, unknown,
263                      static_cast<size_t>(name_length));
264    return std::unique_ptr<char[]>(buffer);
265  }
266}
267
268SourcePositionInfo GetSourcePositionInfo(Handle<Code> code,
269                                         Handle<SharedFunctionInfo> function,
270                                         SourcePosition pos) {
271  if (code->is_turbofanned() || code->is_crankshafted()) {
272    DisallowHeapAllocation disallow;
273    return pos.InliningStack(code)[0];
274  } else {
275    return SourcePositionInfo(pos, function);
276  }
277}
278
279}  // namespace
280
281void PerfJitLogger::LogWriteDebugInfo(Code* code, SharedFunctionInfo* shared) {
282  // Compute the entry count and get the name of the script.
283  uint32_t entry_count = 0;
284  for (SourcePositionTableIterator iterator(code->source_position_table());
285       !iterator.done(); iterator.Advance()) {
286    entry_count++;
287  }
288  if (entry_count == 0) return;
289  Handle<Script> script(Script::cast(shared->script()));
290
291  PerfJitCodeDebugInfo debug_info;
292
293  debug_info.event_ = PerfJitCodeLoad::kDebugInfo;
294  debug_info.time_stamp_ = GetTimestamp();
295  debug_info.address_ = reinterpret_cast<uint64_t>(code->instruction_start());
296  debug_info.entry_count_ = entry_count;
297
298  uint32_t size = sizeof(debug_info);
299  // Add the sizes of fixed parts of entries.
300  size += entry_count * sizeof(PerfJitDebugEntry);
301  // Add the size of the name after each entry.
302
303  Handle<Code> code_handle(code);
304  Handle<SharedFunctionInfo> function_handle(shared);
305  for (SourcePositionTableIterator iterator(code->source_position_table());
306       !iterator.done(); iterator.Advance()) {
307    SourcePositionInfo info(GetSourcePositionInfo(code_handle, function_handle,
308                                                  iterator.source_position()));
309    Handle<Script> script(Script::cast(info.function->script()));
310    std::unique_ptr<char[]> name_string = GetScriptName(script);
311    size += (static_cast<uint32_t>(strlen(name_string.get())) + 1);
312  }
313
314  int padding = ((size + 7) & (~7)) - size;
315  debug_info.size_ = size + padding;
316  LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info));
317
318  Address code_start = code->instruction_start();
319
320  for (SourcePositionTableIterator iterator(code->source_position_table());
321       !iterator.done(); iterator.Advance()) {
322    SourcePositionInfo info(GetSourcePositionInfo(code_handle, function_handle,
323                                                  iterator.source_position()));
324    PerfJitDebugEntry entry;
325    // TODO(danno): There seems to be a bug in the dwarf handling of JIT code in
326    // the perf tool. It seems to erroneously believe that the first instruction
327    // of functions is at offset 0x40 when displayed in "perf report". To
328    // compensate for this, add a magic constant to the position addresses when
329    // writing them out.
330    entry.address_ =
331        reinterpret_cast<intptr_t>(code_start + iterator.code_offset() + 0x40);
332    entry.line_number_ = info.line + 1;
333    entry.column_ = info.column + 1;
334    LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry));
335    Handle<Script> script(Script::cast(info.function->script()));
336    std::unique_ptr<char[]> name_string = GetScriptName(script);
337    LogWriteBytes(name_string.get(),
338                  static_cast<uint32_t>(strlen(name_string.get())) + 1);
339  }
340  char padding_bytes[] = "\0\0\0\0\0\0\0\0";
341  LogWriteBytes(padding_bytes, padding);
342}
343
344void PerfJitLogger::LogWriteUnwindingInfo(Code* code) {
345  PerfJitCodeUnwindingInfo unwinding_info_header;
346  unwinding_info_header.event_ = PerfJitCodeLoad::kUnwindingInfo;
347  unwinding_info_header.time_stamp_ = GetTimestamp();
348  unwinding_info_header.eh_frame_hdr_size_ = EhFrameConstants::kEhFrameHdrSize;
349
350  if (code->has_unwinding_info()) {
351    unwinding_info_header.unwinding_size_ = code->unwinding_info_size();
352    unwinding_info_header.mapped_size_ = unwinding_info_header.unwinding_size_;
353  } else {
354    unwinding_info_header.unwinding_size_ = EhFrameConstants::kEhFrameHdrSize;
355    unwinding_info_header.mapped_size_ = 0;
356  }
357
358  int content_size = static_cast<int>(sizeof(unwinding_info_header) +
359                                      unwinding_info_header.unwinding_size_);
360  int padding_size = RoundUp(content_size, 8) - content_size;
361  unwinding_info_header.size_ = content_size + padding_size;
362
363  LogWriteBytes(reinterpret_cast<const char*>(&unwinding_info_header),
364                sizeof(unwinding_info_header));
365
366  if (code->has_unwinding_info()) {
367    LogWriteBytes(reinterpret_cast<const char*>(code->unwinding_info_start()),
368                  code->unwinding_info_size());
369  } else {
370    OFStream perf_output_stream(perf_output_handle_);
371    EhFrameWriter::WriteEmptyEhFrame(perf_output_stream);
372  }
373
374  char padding_bytes[] = "\0\0\0\0\0\0\0\0";
375  DCHECK_LT(padding_size, static_cast<int>(sizeof(padding_bytes)));
376  LogWriteBytes(padding_bytes, static_cast<int>(padding_size));
377}
378
379void PerfJitLogger::CodeMoveEvent(AbstractCode* from, Address to) {
380  // Code relocation not supported.
381  UNREACHABLE();
382}
383
384void PerfJitLogger::LogWriteBytes(const char* bytes, int size) {
385  size_t rv = fwrite(bytes, 1, size, perf_output_handle_);
386  DCHECK(static_cast<size_t>(size) == rv);
387  USE(rv);
388}
389
390void PerfJitLogger::LogWriteHeader() {
391  DCHECK(perf_output_handle_ != NULL);
392  PerfJitHeader header;
393
394  header.magic_ = PerfJitHeader::kMagic;
395  header.version_ = PerfJitHeader::kVersion;
396  header.size_ = sizeof(header);
397  header.elf_mach_target_ = GetElfMach();
398  header.reserved_ = 0xdeadbeef;
399  header.process_id_ = base::OS::GetCurrentProcessId();
400  header.time_stamp_ =
401      static_cast<uint64_t>(base::OS::TimeCurrentMillis() * 1000.0);
402  header.flags_ = 0;
403
404  LogWriteBytes(reinterpret_cast<const char*>(&header), sizeof(header));
405}
406
407#endif  // V8_OS_LINUX
408}  // namespace internal
409}  // namespace v8
410