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