Line data Source code
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/snapshot/embedded-data.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 :
120 0 : void PerfJitLogger::OpenJitDumpFile() {
121 : // Open the perf JIT dump file.
122 0 : 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 0 : base::OS::GetCurrentProcessId());
128 0 : CHECK_NE(size, -1);
129 :
130 : int fd = open(perf_dump_name.start(), O_CREAT | O_TRUNC | O_RDWR, 0666);
131 0 : if (fd == -1) return;
132 :
133 0 : marker_address_ = OpenMarkerFile(fd);
134 0 : if (marker_address_ == nullptr) return;
135 :
136 0 : perf_output_handle_ = fdopen(fd, "w+");
137 0 : if (perf_output_handle_ == nullptr) return;
138 :
139 0 : setvbuf(perf_output_handle_, nullptr, _IOFBF, kLogBufferSize);
140 : }
141 :
142 0 : void PerfJitLogger::CloseJitDumpFile() {
143 0 : if (perf_output_handle_ == nullptr) return;
144 0 : fclose(perf_output_handle_);
145 0 : perf_output_handle_ = nullptr;
146 : }
147 :
148 0 : void* PerfJitLogger::OpenMarkerFile(int fd) {
149 0 : long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
150 0 : 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 0 : mmap(nullptr, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
157 0 : return (marker_address == MAP_FAILED) ? nullptr : marker_address;
158 : }
159 :
160 0 : void PerfJitLogger::CloseMarkerFile(void* marker_address) {
161 0 : if (marker_address == nullptr) return;
162 0 : long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
163 0 : if (page_size == -1) return;
164 0 : munmap(marker_address, page_size);
165 : }
166 :
167 0 : PerfJitLogger::PerfJitLogger(Isolate* isolate) : CodeEventLogger(isolate) {
168 : base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
169 :
170 0 : reference_count_++;
171 : // If this is the first logger, open the file and write the header.
172 0 : if (reference_count_ == 1) {
173 0 : OpenJitDumpFile();
174 0 : if (perf_output_handle_ == nullptr) return;
175 0 : LogWriteHeader();
176 : }
177 : }
178 :
179 0 : PerfJitLogger::~PerfJitLogger() {
180 : base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
181 :
182 0 : reference_count_--;
183 : // If this was the last logger, close the file.
184 0 : if (reference_count_ == 0) {
185 : CloseJitDumpFile();
186 : }
187 0 : }
188 :
189 0 : uint64_t PerfJitLogger::GetTimestamp() {
190 : struct timespec ts;
191 0 : int result = clock_gettime(CLOCK_MONOTONIC, &ts);
192 : DCHECK_EQ(0, result);
193 : USE(result);
194 : static const uint64_t kNsecPerSec = 1000000000;
195 0 : return (ts.tv_sec * kNsecPerSec) + ts.tv_nsec;
196 : }
197 :
198 0 : void PerfJitLogger::LogRecordedBuffer(AbstractCode abstract_code,
199 : SharedFunctionInfo shared,
200 : const char* name, int length) {
201 0 : if (FLAG_perf_basic_prof_only_functions &&
202 0 : (abstract_code->kind() != AbstractCode::INTERPRETED_FUNCTION &&
203 0 : abstract_code->kind() != AbstractCode::OPTIMIZED_FUNCTION)) {
204 0 : return;
205 : }
206 :
207 : base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
208 :
209 0 : if (perf_output_handle_ == nullptr) return;
210 :
211 : // We only support non-interpreted functions.
212 0 : if (!abstract_code->IsCode()) return;
213 0 : 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 0 : if (FLAG_perf_prof && !shared.is_null()) {
218 : // TODO(herhut): This currently breaks for js2wasm/wasm2js functions.
219 0 : if (code->kind() != Code::JS_TO_WASM_FUNCTION &&
220 : code->kind() != Code::WASM_TO_JS_FUNCTION) {
221 0 : LogWriteDebugInfo(code, shared);
222 : }
223 : }
224 :
225 : const char* code_name = name;
226 0 : 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 0 : : code->InstructionSize();
231 :
232 : // Unwinding info comes right after debug info.
233 0 : if (FLAG_perf_prof_unwinding_info) LogWriteUnwindingInfo(code);
234 :
235 0 : WriteJitCodeLoadEntry(code_pointer, code_size, code_name, length);
236 : }
237 :
238 0 : 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 0 : if (perf_output_handle_ == nullptr) return;
243 :
244 : WriteJitCodeLoadEntry(code->instructions().start(),
245 0 : code->instructions().length(), name, length);
246 : }
247 :
248 0 : 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 0 : code_load.event_ = PerfJitCodeLoad::kLoad;
255 0 : code_load.size_ = sizeof(code_load) + name_length + 1 + code_size;
256 0 : code_load.time_stamp_ = GetTimestamp();
257 : code_load.process_id_ =
258 0 : static_cast<uint32_t>(base::OS::GetCurrentProcessId());
259 0 : code_load.thread_id_ = static_cast<uint32_t>(base::OS::GetCurrentThreadId());
260 0 : code_load.vma_ = reinterpret_cast<uint64_t>(code_pointer);
261 0 : code_load.code_address_ = reinterpret_cast<uint64_t>(code_pointer);
262 0 : code_load.code_size_ = code_size;
263 0 : code_load.code_id_ = code_index_;
264 :
265 0 : 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 0 : LogWriteBytes(reinterpret_cast<const char*>(code_pointer), code_size);
271 0 : }
272 :
273 : namespace {
274 :
275 : constexpr char kUnknownScriptNameString[] = "<unknown>";
276 : constexpr size_t kUnknownScriptNameStringLen =
277 : arraysize(kUnknownScriptNameString) - 1;
278 :
279 0 : size_t GetScriptNameLength(const SourcePositionInfo& info) {
280 0 : if (!info.script.is_null()) {
281 0 : Object name_or_url = info.script->GetNameOrSourceURL();
282 0 : if (name_or_url->IsString()) {
283 0 : String str = String::cast(name_or_url);
284 0 : if (str->IsOneByteRepresentation()) return str->length();
285 : int length;
286 0 : str->ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length);
287 0 : return static_cast<size_t>(length);
288 : }
289 : }
290 : return kUnknownScriptNameStringLen;
291 : }
292 :
293 0 : Vector<const char> GetScriptName(const SourcePositionInfo& info,
294 : std::unique_ptr<char[]>* storage,
295 : const DisallowHeapAllocation& no_gc) {
296 0 : if (!info.script.is_null()) {
297 0 : Object name_or_url = info.script->GetNameOrSourceURL();
298 0 : if (name_or_url->IsSeqOneByteString()) {
299 : SeqOneByteString str = SeqOneByteString::cast(name_or_url);
300 : return {reinterpret_cast<char*>(str->GetChars(no_gc)),
301 0 : static_cast<size_t>(str->length())};
302 0 : } else if (name_or_url->IsString()) {
303 : int length;
304 0 : *storage =
305 : String::cast(name_or_url)
306 : ->ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length);
307 0 : return {storage->get(), static_cast<size_t>(length)};
308 : }
309 : }
310 0 : return {kUnknownScriptNameString, kUnknownScriptNameStringLen};
311 : }
312 :
313 0 : SourcePositionInfo GetSourcePositionInfo(Handle<Code> code,
314 : Handle<SharedFunctionInfo> function,
315 : SourcePosition pos) {
316 0 : if (code->is_turbofanned()) {
317 : DisallowHeapAllocation disallow;
318 0 : return pos.InliningStack(code)[0];
319 : } else {
320 0 : return SourcePositionInfo(pos, function);
321 : }
322 : }
323 :
324 : } // namespace
325 :
326 0 : void PerfJitLogger::LogWriteDebugInfo(Code code, SharedFunctionInfo shared) {
327 : // Compute the entry count and get the name of the script.
328 : uint32_t entry_count = 0;
329 0 : for (SourcePositionTableIterator iterator(code->SourcePositionTable());
330 0 : !iterator.done(); iterator.Advance()) {
331 0 : entry_count++;
332 : }
333 0 : if (entry_count == 0) return;
334 : // The WasmToJS wrapper stubs have source position entries.
335 0 : if (!shared->HasSourceCode()) return;
336 : Isolate* isolate = shared->GetIsolate();
337 0 : Handle<Script> script(Script::cast(shared->script()), isolate);
338 :
339 : PerfJitCodeDebugInfo debug_info;
340 :
341 0 : debug_info.event_ = PerfJitCodeLoad::kDebugInfo;
342 0 : debug_info.time_stamp_ = GetTimestamp();
343 0 : debug_info.address_ = code->InstructionStart();
344 0 : debug_info.entry_count_ = entry_count;
345 :
346 : uint32_t size = sizeof(debug_info);
347 : // Add the sizes of fixed parts of entries.
348 0 : size += entry_count * sizeof(PerfJitDebugEntry);
349 : // Add the size of the name after each entry.
350 :
351 : Handle<Code> code_handle(code, isolate);
352 : Handle<SharedFunctionInfo> function_handle(shared, isolate);
353 0 : for (SourcePositionTableIterator iterator(code->SourcePositionTable());
354 0 : !iterator.done(); iterator.Advance()) {
355 : SourcePositionInfo info(GetSourcePositionInfo(code_handle, function_handle,
356 0 : iterator.source_position()));
357 0 : size += GetScriptNameLength(info) + 1;
358 : }
359 :
360 0 : int padding = ((size + 7) & (~7)) - size;
361 0 : debug_info.size_ = size + padding;
362 : LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info));
363 :
364 0 : Address code_start = code->InstructionStart();
365 :
366 0 : for (SourcePositionTableIterator iterator(code->SourcePositionTable());
367 0 : !iterator.done(); iterator.Advance()) {
368 : SourcePositionInfo info(GetSourcePositionInfo(code_handle, function_handle,
369 0 : iterator.source_position()));
370 : PerfJitDebugEntry entry;
371 : // The entry point of the function will be placed straight after the ELF
372 : // header when processed by "perf inject". Adjust the position addresses
373 : // accordingly.
374 0 : entry.address_ = code_start + iterator.code_offset() + kElfHeaderSize;
375 0 : entry.line_number_ = info.line + 1;
376 0 : entry.column_ = info.column + 1;
377 : LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry));
378 : // The extracted name may point into heap-objects, thus disallow GC.
379 : DisallowHeapAllocation no_gc;
380 0 : std::unique_ptr<char[]> name_storage;
381 0 : Vector<const char> name_string = GetScriptName(info, &name_storage, no_gc);
382 : LogWriteBytes(name_string.start(),
383 0 : static_cast<uint32_t>(name_string.size()) + 1);
384 : }
385 0 : char padding_bytes[8] = {0};
386 : LogWriteBytes(padding_bytes, padding);
387 : }
388 :
389 0 : void PerfJitLogger::LogWriteUnwindingInfo(Code code) {
390 : PerfJitCodeUnwindingInfo unwinding_info_header;
391 0 : unwinding_info_header.event_ = PerfJitCodeLoad::kUnwindingInfo;
392 0 : unwinding_info_header.time_stamp_ = GetTimestamp();
393 0 : unwinding_info_header.eh_frame_hdr_size_ = EhFrameConstants::kEhFrameHdrSize;
394 :
395 0 : if (code->has_unwinding_info()) {
396 0 : unwinding_info_header.unwinding_size_ = code->unwinding_info_size();
397 0 : unwinding_info_header.mapped_size_ = unwinding_info_header.unwinding_size_;
398 : } else {
399 0 : unwinding_info_header.unwinding_size_ = EhFrameConstants::kEhFrameHdrSize;
400 0 : unwinding_info_header.mapped_size_ = 0;
401 : }
402 :
403 : int content_size = static_cast<int>(sizeof(unwinding_info_header) +
404 0 : unwinding_info_header.unwinding_size_);
405 0 : int padding_size = RoundUp(content_size, 8) - content_size;
406 0 : unwinding_info_header.size_ = content_size + padding_size;
407 :
408 : LogWriteBytes(reinterpret_cast<const char*>(&unwinding_info_header),
409 : sizeof(unwinding_info_header));
410 :
411 0 : if (code->has_unwinding_info()) {
412 : LogWriteBytes(reinterpret_cast<const char*>(code->unwinding_info_start()),
413 0 : code->unwinding_info_size());
414 : } else {
415 0 : OFStream perf_output_stream(perf_output_handle_);
416 0 : EhFrameWriter::WriteEmptyEhFrame(perf_output_stream);
417 : }
418 :
419 0 : char padding_bytes[] = "\0\0\0\0\0\0\0\0";
420 : DCHECK_LT(padding_size, static_cast<int>(sizeof(padding_bytes)));
421 : LogWriteBytes(padding_bytes, static_cast<int>(padding_size));
422 0 : }
423 :
424 0 : void PerfJitLogger::CodeMoveEvent(AbstractCode from, AbstractCode to) {
425 : // We may receive a CodeMove event if a BytecodeArray object moves. Otherwise
426 : // code relocation is not supported.
427 0 : CHECK(from->IsBytecodeArray());
428 0 : }
429 :
430 0 : void PerfJitLogger::LogWriteBytes(const char* bytes, int size) {
431 0 : size_t rv = fwrite(bytes, 1, size, perf_output_handle_);
432 : DCHECK(static_cast<size_t>(size) == rv);
433 : USE(rv);
434 0 : }
435 :
436 0 : void PerfJitLogger::LogWriteHeader() {
437 : DCHECK_NOT_NULL(perf_output_handle_);
438 : PerfJitHeader header;
439 :
440 0 : header.magic_ = PerfJitHeader::kMagic;
441 0 : header.version_ = PerfJitHeader::kVersion;
442 0 : header.size_ = sizeof(header);
443 0 : header.elf_mach_target_ = GetElfMach();
444 0 : header.reserved_ = 0xDEADBEEF;
445 0 : header.process_id_ = base::OS::GetCurrentProcessId();
446 : header.time_stamp_ =
447 0 : static_cast<uint64_t>(V8::GetCurrentPlatform()->CurrentClockTimeMillis() *
448 0 : base::Time::kMicrosecondsPerMillisecond);
449 0 : header.flags_ = 0;
450 :
451 : LogWriteBytes(reinterpret_cast<const char*>(&header), sizeof(header));
452 0 : }
453 :
454 : #endif // V8_OS_LINUX
455 : } // namespace internal
456 183867 : } // namespace v8
|