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