• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/instruction-stream.h"
35 #include "src/objects-inl.h"
36 #include "src/source-position-table.h"
37 #include "src/wasm/wasm-code-manager.h"
38 
39 #if V8_OS_LINUX
40 #include <fcntl.h>
41 #include <sys/mman.h>
42 #undef MAP_TYPE  // jumbo: conflicts with v8::internal::InstanceType::MAP_TYPE
43 #include <unistd.h>
44 #endif  // V8_OS_LINUX
45 
46 namespace v8 {
47 namespace internal {
48 
49 #if V8_OS_LINUX
50 
51 struct PerfJitHeader {
52   uint32_t magic_;
53   uint32_t version_;
54   uint32_t size_;
55   uint32_t elf_mach_target_;
56   uint32_t reserved_;
57   uint32_t process_id_;
58   uint64_t time_stamp_;
59   uint64_t flags_;
60 
61   static const uint32_t kMagic = 0x4A695444;
62   static const uint32_t kVersion = 1;
63 };
64 
65 struct PerfJitBase {
66   enum PerfJitEvent {
67     kLoad = 0,
68     kMove = 1,
69     kDebugInfo = 2,
70     kClose = 3,
71     kUnwindingInfo = 4
72   };
73 
74   uint32_t event_;
75   uint32_t size_;
76   uint64_t time_stamp_;
77 };
78 
79 struct PerfJitCodeLoad : PerfJitBase {
80   uint32_t process_id_;
81   uint32_t thread_id_;
82   uint64_t vma_;
83   uint64_t code_address_;
84   uint64_t code_size_;
85   uint64_t code_id_;
86 };
87 
88 struct PerfJitDebugEntry {
89   uint64_t address_;
90   int line_number_;
91   int column_;
92   // Followed by null-terminated name or \0xFF\0 if same as previous.
93 };
94 
95 struct PerfJitCodeDebugInfo : PerfJitBase {
96   uint64_t address_;
97   uint64_t entry_count_;
98   // Followed by entry_count_ instances of PerfJitDebugEntry.
99 };
100 
101 struct PerfJitCodeUnwindingInfo : PerfJitBase {
102   uint64_t unwinding_size_;
103   uint64_t eh_frame_hdr_size_;
104   uint64_t mapped_size_;
105   // Followed by size_ - sizeof(PerfJitCodeUnwindingInfo) bytes of data.
106 };
107 
108 const char PerfJitLogger::kFilenameFormatString[] = "./jit-%d.dump";
109 
110 // Extra padding for the PID in the filename
111 const int PerfJitLogger::kFilenameBufferPadding = 16;
112 
113 base::LazyRecursiveMutex PerfJitLogger::file_mutex_;
114 // The following static variables are protected by PerfJitLogger::file_mutex_.
115 uint64_t PerfJitLogger::reference_count_ = 0;
116 void* PerfJitLogger::marker_address_ = nullptr;
117 uint64_t PerfJitLogger::code_index_ = 0;
118 FILE* PerfJitLogger::perf_output_handle_ = nullptr;
119 
OpenJitDumpFile()120 void PerfJitLogger::OpenJitDumpFile() {
121   // Open the perf JIT dump file.
122   perf_output_handle_ = nullptr;
123 
124   int bufferSize = sizeof(kFilenameFormatString) + kFilenameBufferPadding;
125   ScopedVector<char> perf_dump_name(bufferSize);
126   int size = SNPrintF(perf_dump_name, kFilenameFormatString,
127                       base::OS::GetCurrentProcessId());
128   CHECK_NE(size, -1);
129 
130   int fd = open(perf_dump_name.start(), O_CREAT | O_TRUNC | O_RDWR, 0666);
131   if (fd == -1) return;
132 
133   marker_address_ = OpenMarkerFile(fd);
134   if (marker_address_ == nullptr) return;
135 
136   perf_output_handle_ = fdopen(fd, "w+");
137   if (perf_output_handle_ == nullptr) return;
138 
139   setvbuf(perf_output_handle_, nullptr, _IOFBF, kLogBufferSize);
140 }
141 
CloseJitDumpFile()142 void PerfJitLogger::CloseJitDumpFile() {
143   if (perf_output_handle_ == nullptr) return;
144   fclose(perf_output_handle_);
145   perf_output_handle_ = nullptr;
146 }
147 
OpenMarkerFile(int fd)148 void* PerfJitLogger::OpenMarkerFile(int fd) {
149   long page_size = sysconf(_SC_PAGESIZE);  // NOLINT(runtime/int)
150   if (page_size == -1) return nullptr;
151 
152   // Mmap the file so that there is a mmap record in the perf_data file.
153   //
154   // The map must be PROT_EXEC to ensure it is not ignored by perf record.
155   void* marker_address =
156       mmap(nullptr, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
157   return (marker_address == MAP_FAILED) ? nullptr : marker_address;
158 }
159 
CloseMarkerFile(void * marker_address)160 void PerfJitLogger::CloseMarkerFile(void* marker_address) {
161   if (marker_address == nullptr) return;
162   long page_size = sysconf(_SC_PAGESIZE);  // NOLINT(runtime/int)
163   if (page_size == -1) return;
164   munmap(marker_address, page_size);
165 }
166 
PerfJitLogger(Isolate * isolate)167 PerfJitLogger::PerfJitLogger(Isolate* isolate) : CodeEventLogger(isolate) {
168   base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
169 
170   reference_count_++;
171   // If this is the first logger, open the file and write the header.
172   if (reference_count_ == 1) {
173     OpenJitDumpFile();
174     if (perf_output_handle_ == nullptr) return;
175     LogWriteHeader();
176   }
177 }
178 
~PerfJitLogger()179 PerfJitLogger::~PerfJitLogger() {
180   base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
181 
182   reference_count_--;
183   // If this was the last logger, close the file.
184   if (reference_count_ == 0) {
185     CloseJitDumpFile();
186   }
187 }
188 
GetTimestamp()189 uint64_t PerfJitLogger::GetTimestamp() {
190   struct timespec ts;
191   int result = clock_gettime(CLOCK_MONOTONIC, &ts);
192   DCHECK_EQ(0, result);
193   USE(result);
194   static const uint64_t kNsecPerSec = 1000000000;
195   return (ts.tv_sec * kNsecPerSec) + ts.tv_nsec;
196 }
197 
LogRecordedBuffer(AbstractCode * abstract_code,SharedFunctionInfo * shared,const char * name,int length)198 void PerfJitLogger::LogRecordedBuffer(AbstractCode* abstract_code,
199                                       SharedFunctionInfo* shared,
200                                       const char* name, int length) {
201   if (FLAG_perf_basic_prof_only_functions &&
202       (abstract_code->kind() != AbstractCode::INTERPRETED_FUNCTION &&
203        abstract_code->kind() != AbstractCode::OPTIMIZED_FUNCTION)) {
204     return;
205   }
206 
207   base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
208 
209   if (perf_output_handle_ == nullptr) return;
210 
211   // We only support non-interpreted functions.
212   if (!abstract_code->IsCode()) return;
213   Code* code = abstract_code->GetCode();
214   DCHECK(code->raw_instruction_start() == code->address() + Code::kHeaderSize);
215 
216   // Debug info has to be emitted first.
217   if (FLAG_perf_prof && shared != nullptr) {
218     // TODO(herhut): This currently breaks for js2wasm/wasm2js functions.
219     if (code->kind() != Code::JS_TO_WASM_FUNCTION &&
220         code->kind() != Code::WASM_TO_JS_FUNCTION) {
221       LogWriteDebugInfo(code, shared);
222     }
223   }
224 
225   const char* code_name = name;
226   uint8_t* code_pointer = reinterpret_cast<uint8_t*>(code->InstructionStart());
227   // Code generated by Turbofan will have the safepoint table directly after
228   // instructions. There is no need to record the safepoint table itself.
229   uint32_t code_size = code->is_turbofanned() ? code->safepoint_table_offset()
230                                               : code->InstructionSize();
231 
232   // Unwinding info comes right after debug info.
233   if (FLAG_perf_prof_unwinding_info) LogWriteUnwindingInfo(code);
234 
235   WriteJitCodeLoadEntry(code_pointer, code_size, code_name, length);
236 }
237 
LogRecordedBuffer(const wasm::WasmCode * code,const char * name,int length)238 void PerfJitLogger::LogRecordedBuffer(const wasm::WasmCode* code,
239                                       const char* name, int length) {
240   base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
241 
242   if (perf_output_handle_ == nullptr) return;
243 
244   WriteJitCodeLoadEntry(code->instructions().start(),
245                         code->instructions().length(), name, length);
246 }
247 
WriteJitCodeLoadEntry(const uint8_t * code_pointer,uint32_t code_size,const char * name,int name_length)248 void PerfJitLogger::WriteJitCodeLoadEntry(const uint8_t* code_pointer,
249                                           uint32_t code_size, const char* name,
250                                           int name_length) {
251   static const char string_terminator[] = "\0";
252 
253   PerfJitCodeLoad code_load;
254   code_load.event_ = PerfJitCodeLoad::kLoad;
255   code_load.size_ = sizeof(code_load) + name_length + 1 + code_size;
256   code_load.time_stamp_ = GetTimestamp();
257   code_load.process_id_ =
258       static_cast<uint32_t>(base::OS::GetCurrentProcessId());
259   code_load.thread_id_ = static_cast<uint32_t>(base::OS::GetCurrentThreadId());
260   code_load.vma_ = reinterpret_cast<uint64_t>(code_pointer);
261   code_load.code_address_ = reinterpret_cast<uint64_t>(code_pointer);
262   code_load.code_size_ = code_size;
263   code_load.code_id_ = code_index_;
264 
265   code_index_++;
266 
267   LogWriteBytes(reinterpret_cast<const char*>(&code_load), sizeof(code_load));
268   LogWriteBytes(name, name_length);
269   LogWriteBytes(string_terminator, 1);
270   LogWriteBytes(reinterpret_cast<const char*>(code_pointer), code_size);
271 }
272 
273 namespace {
274 
275 constexpr char kUnknownScriptNameString[] = "<unknown>";
276 constexpr size_t kUnknownScriptNameStringLen =
277     arraysize(kUnknownScriptNameString) - 1;
278 
GetScriptNameLength(const SourcePositionInfo & info)279 size_t GetScriptNameLength(const SourcePositionInfo& info) {
280   if (!info.script.is_null()) {
281     Object* name_or_url = info.script->GetNameOrSourceURL();
282     if (name_or_url->IsString()) {
283       String* str = String::cast(name_or_url);
284       if (str->IsOneByteRepresentation()) return str->length();
285       int length;
286       str->ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length);
287       return static_cast<size_t>(length);
288     }
289   }
290   return kUnknownScriptNameStringLen;
291 }
292 
GetScriptName(const SourcePositionInfo & info,std::unique_ptr<char[]> * storage)293 Vector<const char> GetScriptName(const SourcePositionInfo& info,
294                                  std::unique_ptr<char[]>* storage) {
295   if (!info.script.is_null()) {
296     Object* name_or_url = info.script->GetNameOrSourceURL();
297     if (name_or_url->IsSeqOneByteString()) {
298       SeqOneByteString* str = SeqOneByteString::cast(name_or_url);
299       return {reinterpret_cast<char*>(str->GetChars()),
300               static_cast<size_t>(str->length())};
301     } else if (name_or_url->IsString()) {
302       int length;
303       *storage =
304           String::cast(name_or_url)
305               ->ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length);
306       return {storage->get(), static_cast<size_t>(length)};
307     }
308   }
309   return {kUnknownScriptNameString, kUnknownScriptNameStringLen};
310 }
311 
GetSourcePositionInfo(Handle<Code> code,Handle<SharedFunctionInfo> function,SourcePosition pos)312 SourcePositionInfo GetSourcePositionInfo(Handle<Code> code,
313                                          Handle<SharedFunctionInfo> function,
314                                          SourcePosition pos) {
315   if (code->is_turbofanned()) {
316     DisallowHeapAllocation disallow;
317     return pos.InliningStack(code)[0];
318   } else {
319     return SourcePositionInfo(pos, function);
320   }
321 }
322 
323 }  // namespace
324 
LogWriteDebugInfo(Code * code,SharedFunctionInfo * shared)325 void PerfJitLogger::LogWriteDebugInfo(Code* code, SharedFunctionInfo* shared) {
326   // Compute the entry count and get the name of the script.
327   uint32_t entry_count = 0;
328   for (SourcePositionTableIterator iterator(code->SourcePositionTable());
329        !iterator.done(); iterator.Advance()) {
330     entry_count++;
331   }
332   if (entry_count == 0) return;
333   // The WasmToJS wrapper stubs have source position entries.
334   if (!shared->HasSourceCode()) return;
335   Isolate* isolate = shared->GetIsolate();
336   Handle<Script> script(Script::cast(shared->script()), isolate);
337 
338   PerfJitCodeDebugInfo debug_info;
339 
340   debug_info.event_ = PerfJitCodeLoad::kDebugInfo;
341   debug_info.time_stamp_ = GetTimestamp();
342   debug_info.address_ = code->InstructionStart();
343   debug_info.entry_count_ = entry_count;
344 
345   uint32_t size = sizeof(debug_info);
346   // Add the sizes of fixed parts of entries.
347   size += entry_count * sizeof(PerfJitDebugEntry);
348   // Add the size of the name after each entry.
349 
350   Handle<Code> code_handle(code, isolate);
351   Handle<SharedFunctionInfo> function_handle(shared, isolate);
352   for (SourcePositionTableIterator iterator(code->SourcePositionTable());
353        !iterator.done(); iterator.Advance()) {
354     SourcePositionInfo info(GetSourcePositionInfo(code_handle, function_handle,
355                                                   iterator.source_position()));
356     size += GetScriptNameLength(info) + 1;
357   }
358 
359   int padding = ((size + 7) & (~7)) - size;
360   debug_info.size_ = size + padding;
361   LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info));
362 
363   Address code_start = code->InstructionStart();
364 
365   for (SourcePositionTableIterator iterator(code->SourcePositionTable());
366        !iterator.done(); iterator.Advance()) {
367     SourcePositionInfo info(GetSourcePositionInfo(code_handle, function_handle,
368                                                   iterator.source_position()));
369     PerfJitDebugEntry entry;
370     // The entry point of the function will be placed straight after the ELF
371     // header when processed by "perf inject". Adjust the position addresses
372     // accordingly.
373     entry.address_ = code_start + iterator.code_offset() + kElfHeaderSize;
374     entry.line_number_ = info.line + 1;
375     entry.column_ = info.column + 1;
376     LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry));
377     // The extracted name may point into heap-objects, thus disallow GC.
378     DisallowHeapAllocation no_gc;
379     std::unique_ptr<char[]> name_storage;
380     Vector<const char> name_string = GetScriptName(info, &name_storage);
381     LogWriteBytes(name_string.start(),
382                   static_cast<uint32_t>(name_string.size()) + 1);
383   }
384   char padding_bytes[8] = {0};
385   LogWriteBytes(padding_bytes, padding);
386 }
387 
LogWriteUnwindingInfo(Code * code)388 void PerfJitLogger::LogWriteUnwindingInfo(Code* code) {
389   PerfJitCodeUnwindingInfo unwinding_info_header;
390   unwinding_info_header.event_ = PerfJitCodeLoad::kUnwindingInfo;
391   unwinding_info_header.time_stamp_ = GetTimestamp();
392   unwinding_info_header.eh_frame_hdr_size_ = EhFrameConstants::kEhFrameHdrSize;
393 
394   if (code->has_unwinding_info()) {
395     unwinding_info_header.unwinding_size_ = code->unwinding_info_size();
396     unwinding_info_header.mapped_size_ = unwinding_info_header.unwinding_size_;
397   } else {
398     unwinding_info_header.unwinding_size_ = EhFrameConstants::kEhFrameHdrSize;
399     unwinding_info_header.mapped_size_ = 0;
400   }
401 
402   int content_size = static_cast<int>(sizeof(unwinding_info_header) +
403                                       unwinding_info_header.unwinding_size_);
404   int padding_size = RoundUp(content_size, 8) - content_size;
405   unwinding_info_header.size_ = content_size + padding_size;
406 
407   LogWriteBytes(reinterpret_cast<const char*>(&unwinding_info_header),
408                 sizeof(unwinding_info_header));
409 
410   if (code->has_unwinding_info()) {
411     LogWriteBytes(reinterpret_cast<const char*>(code->unwinding_info_start()),
412                   code->unwinding_info_size());
413   } else {
414     OFStream perf_output_stream(perf_output_handle_);
415     EhFrameWriter::WriteEmptyEhFrame(perf_output_stream);
416   }
417 
418   char padding_bytes[] = "\0\0\0\0\0\0\0\0";
419   DCHECK_LT(padding_size, static_cast<int>(sizeof(padding_bytes)));
420   LogWriteBytes(padding_bytes, static_cast<int>(padding_size));
421 }
422 
CodeMoveEvent(AbstractCode * from,AbstractCode * to)423 void PerfJitLogger::CodeMoveEvent(AbstractCode* from, AbstractCode* to) {
424   // We may receive a CodeMove event if a BytecodeArray object moves. Otherwise
425   // code relocation is not supported.
426   CHECK(from->IsBytecodeArray());
427 }
428 
LogWriteBytes(const char * bytes,int size)429 void PerfJitLogger::LogWriteBytes(const char* bytes, int size) {
430   size_t rv = fwrite(bytes, 1, size, perf_output_handle_);
431   DCHECK(static_cast<size_t>(size) == rv);
432   USE(rv);
433 }
434 
LogWriteHeader()435 void PerfJitLogger::LogWriteHeader() {
436   DCHECK_NOT_NULL(perf_output_handle_);
437   PerfJitHeader header;
438 
439   header.magic_ = PerfJitHeader::kMagic;
440   header.version_ = PerfJitHeader::kVersion;
441   header.size_ = sizeof(header);
442   header.elf_mach_target_ = GetElfMach();
443   header.reserved_ = 0xDEADBEEF;
444   header.process_id_ = base::OS::GetCurrentProcessId();
445   header.time_stamp_ =
446       static_cast<uint64_t>(V8::GetCurrentPlatform()->CurrentClockTimeMillis() *
447                             base::Time::kMicrosecondsPerMillisecond);
448   header.flags_ = 0;
449 
450   LogWriteBytes(reinterpret_cast<const char*>(&header), sizeof(header));
451 }
452 
453 #endif  // V8_OS_LINUX
454 }  // namespace internal
455 }  // namespace v8
456