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/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 :
47 : namespace v8 {
48 : namespace internal {
49 :
50 : #if V8_OS_LINUX
51 :
52 : struct 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 :
66 : struct 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 :
80 : struct 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 :
89 : struct 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 :
96 : struct PerfJitCodeDebugInfo : PerfJitBase {
97 : uint64_t address_;
98 : uint64_t entry_count_;
99 : // Followed by entry_count_ instances of PerfJitDebugEntry.
100 : };
101 :
102 : struct 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 :
109 : const char PerfJitLogger::kFilenameFormatString[] = "./jit-%d.dump";
110 :
111 : // Extra padding for the PID in the filename
112 : const int PerfJitLogger::kFilenameBufferPadding = 16;
113 :
114 : base::LazyRecursiveMutex PerfJitLogger::file_mutex_;
115 : // The following static variables are protected by PerfJitLogger::file_mutex_.
116 : uint64_t PerfJitLogger::reference_count_ = 0;
117 : void* PerfJitLogger::marker_address_ = nullptr;
118 : uint64_t PerfJitLogger::code_index_ = 0;
119 : FILE* PerfJitLogger::perf_output_handle_ = nullptr;
120 :
121 0 : void PerfJitLogger::OpenJitDumpFile() {
122 : // Open the perf JIT dump file.
123 0 : perf_output_handle_ = nullptr;
124 :
125 : int bufferSize = sizeof(kFilenameFormatString) + kFilenameBufferPadding;
126 : ScopedVector<char> perf_dump_name(bufferSize);
127 0 : int size = SNPrintF(perf_dump_name, kFilenameFormatString,
128 0 : base::OS::GetCurrentProcessId());
129 0 : CHECK_NE(size, -1);
130 :
131 : int fd = open(perf_dump_name.start(), O_CREAT | O_TRUNC | O_RDWR, 0666);
132 0 : if (fd == -1) return;
133 :
134 0 : marker_address_ = OpenMarkerFile(fd);
135 0 : if (marker_address_ == nullptr) return;
136 :
137 0 : perf_output_handle_ = fdopen(fd, "w+");
138 0 : if (perf_output_handle_ == nullptr) return;
139 :
140 0 : setvbuf(perf_output_handle_, nullptr, _IOFBF, kLogBufferSize);
141 : }
142 :
143 0 : void PerfJitLogger::CloseJitDumpFile() {
144 0 : if (perf_output_handle_ == nullptr) return;
145 0 : fclose(perf_output_handle_);
146 0 : perf_output_handle_ = nullptr;
147 : }
148 :
149 0 : void* PerfJitLogger::OpenMarkerFile(int fd) {
150 0 : long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
151 0 : 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 0 : mmap(nullptr, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
158 0 : return (marker_address == MAP_FAILED) ? nullptr : marker_address;
159 : }
160 :
161 0 : void PerfJitLogger::CloseMarkerFile(void* marker_address) {
162 0 : if (marker_address == nullptr) return;
163 0 : long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
164 0 : if (page_size == -1) return;
165 0 : munmap(marker_address, page_size);
166 : }
167 :
168 0 : PerfJitLogger::PerfJitLogger(Isolate* isolate) : CodeEventLogger(isolate) {
169 : base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
170 :
171 0 : reference_count_++;
172 : // If this is the first logger, open the file and write the header.
173 0 : if (reference_count_ == 1) {
174 0 : OpenJitDumpFile();
175 0 : if (perf_output_handle_ == nullptr) return;
176 0 : LogWriteHeader();
177 : }
178 : }
179 :
180 0 : PerfJitLogger::~PerfJitLogger() {
181 : base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
182 :
183 0 : reference_count_--;
184 : // If this was the last logger, close the file.
185 0 : if (reference_count_ == 0) {
186 : CloseJitDumpFile();
187 : }
188 0 : }
189 :
190 0 : uint64_t PerfJitLogger::GetTimestamp() {
191 : struct timespec ts;
192 0 : int result = clock_gettime(CLOCK_MONOTONIC, &ts);
193 : DCHECK_EQ(0, result);
194 : USE(result);
195 : static const uint64_t kNsecPerSec = 1000000000;
196 0 : return (ts.tv_sec * kNsecPerSec) + ts.tv_nsec;
197 : }
198 :
199 0 : void PerfJitLogger::LogRecordedBuffer(AbstractCode abstract_code,
200 : SharedFunctionInfo shared,
201 : const char* name, int length) {
202 0 : if (FLAG_perf_basic_prof_only_functions &&
203 0 : (abstract_code->kind() != AbstractCode::INTERPRETED_FUNCTION &&
204 0 : abstract_code->kind() != AbstractCode::OPTIMIZED_FUNCTION)) {
205 0 : return;
206 : }
207 :
208 : base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
209 :
210 0 : if (perf_output_handle_ == nullptr) return;
211 :
212 : // We only support non-interpreted functions.
213 0 : if (!abstract_code->IsCode()) return;
214 0 : 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 0 : if (FLAG_perf_prof && !shared.is_null()) {
219 : // TODO(herhut): This currently breaks for js2wasm/wasm2js functions.
220 0 : if (code->kind() != Code::JS_TO_WASM_FUNCTION &&
221 : code->kind() != Code::WASM_TO_JS_FUNCTION) {
222 0 : LogWriteDebugInfo(code, shared);
223 : }
224 : }
225 :
226 : const char* code_name = name;
227 0 : 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 0 : uint32_t code_size = code->ExecutableInstructionSize();
232 :
233 : // Unwinding info comes right after debug info.
234 0 : if (FLAG_perf_prof_unwinding_info) LogWriteUnwindingInfo(code);
235 :
236 0 : WriteJitCodeLoadEntry(code_pointer, code_size, code_name, length);
237 : }
238 :
239 0 : void PerfJitLogger::LogRecordedBuffer(const wasm::WasmCode* code,
240 : const char* name, int length) {
241 : base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
242 :
243 0 : if (perf_output_handle_ == nullptr) return;
244 :
245 0 : WriteJitCodeLoadEntry(code->instructions().start(),
246 0 : code->instructions().length(), name, length);
247 : }
248 :
249 0 : void 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 0 : code_load.event_ = PerfJitCodeLoad::kLoad;
256 0 : code_load.size_ = sizeof(code_load) + name_length + 1 + code_size;
257 0 : code_load.time_stamp_ = GetTimestamp();
258 : code_load.process_id_ =
259 0 : static_cast<uint32_t>(base::OS::GetCurrentProcessId());
260 0 : code_load.thread_id_ = static_cast<uint32_t>(base::OS::GetCurrentThreadId());
261 0 : code_load.vma_ = reinterpret_cast<uint64_t>(code_pointer);
262 0 : code_load.code_address_ = reinterpret_cast<uint64_t>(code_pointer);
263 0 : code_load.code_size_ = code_size;
264 0 : code_load.code_id_ = code_index_;
265 :
266 0 : 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 0 : LogWriteBytes(reinterpret_cast<const char*>(code_pointer), code_size);
272 0 : }
273 :
274 : namespace {
275 :
276 : constexpr char kUnknownScriptNameString[] = "<unknown>";
277 : constexpr size_t kUnknownScriptNameStringLen =
278 : arraysize(kUnknownScriptNameString) - 1;
279 :
280 0 : size_t GetScriptNameLength(const SourcePositionInfo& info) {
281 0 : if (!info.script.is_null()) {
282 0 : Object name_or_url = info.script->GetNameOrSourceURL();
283 0 : if (name_or_url->IsString()) {
284 0 : String str = String::cast(name_or_url);
285 0 : if (str->IsOneByteRepresentation()) return str->length();
286 : int length;
287 0 : str->ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length);
288 0 : return static_cast<size_t>(length);
289 : }
290 : }
291 : return kUnknownScriptNameStringLen;
292 : }
293 :
294 0 : Vector<const char> GetScriptName(const SourcePositionInfo& info,
295 : std::unique_ptr<char[]>* storage,
296 : const DisallowHeapAllocation& no_gc) {
297 0 : if (!info.script.is_null()) {
298 0 : Object name_or_url = info.script->GetNameOrSourceURL();
299 0 : if (name_or_url->IsSeqOneByteString()) {
300 : SeqOneByteString str = SeqOneByteString::cast(name_or_url);
301 : return {reinterpret_cast<char*>(str->GetChars(no_gc)),
302 0 : static_cast<size_t>(str->length())};
303 0 : } else if (name_or_url->IsString()) {
304 : int length;
305 : *storage =
306 0 : String::cast(name_or_url)
307 0 : ->ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length);
308 0 : return {storage->get(), static_cast<size_t>(length)};
309 : }
310 : }
311 0 : return {kUnknownScriptNameString, kUnknownScriptNameStringLen};
312 : }
313 :
314 0 : SourcePositionInfo GetSourcePositionInfo(Handle<Code> code,
315 : Handle<SharedFunctionInfo> function,
316 : SourcePosition pos) {
317 0 : if (code->is_turbofanned()) {
318 : DisallowHeapAllocation disallow;
319 0 : return pos.InliningStack(code)[0];
320 : } else {
321 0 : return SourcePositionInfo(pos, function);
322 : }
323 : }
324 :
325 : } // namespace
326 :
327 0 : void 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 0 : for (SourcePositionTableIterator iterator(code->SourcePositionTable());
331 0 : !iterator.done(); iterator.Advance()) {
332 0 : entry_count++;
333 : }
334 0 : if (entry_count == 0) return;
335 : // The WasmToJS wrapper stubs have source position entries.
336 0 : if (!shared->HasSourceCode()) return;
337 : Isolate* isolate = shared->GetIsolate();
338 0 : Handle<Script> script(Script::cast(shared->script()), isolate);
339 :
340 : PerfJitCodeDebugInfo debug_info;
341 :
342 0 : debug_info.event_ = PerfJitCodeLoad::kDebugInfo;
343 0 : debug_info.time_stamp_ = GetTimestamp();
344 0 : debug_info.address_ = code->InstructionStart();
345 0 : debug_info.entry_count_ = entry_count;
346 :
347 : uint32_t size = sizeof(debug_info);
348 : // Add the sizes of fixed parts of entries.
349 0 : 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 0 : for (SourcePositionTableIterator iterator(code->SourcePositionTable());
355 0 : !iterator.done(); iterator.Advance()) {
356 : SourcePositionInfo info(GetSourcePositionInfo(code_handle, function_handle,
357 0 : iterator.source_position()));
358 0 : size += GetScriptNameLength(info) + 1;
359 : }
360 :
361 0 : int padding = ((size + 7) & (~7)) - size;
362 0 : 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 0 : for (SourcePositionTableIterator iterator(code->SourcePositionTable());
368 0 : !iterator.done(); iterator.Advance()) {
369 : SourcePositionInfo info(GetSourcePositionInfo(code_handle, function_handle,
370 0 : 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 0 : entry.address_ = code_start + iterator.code_offset() + kElfHeaderSize;
376 0 : entry.line_number_ = info.line + 1;
377 0 : 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 0 : std::unique_ptr<char[]> name_storage;
382 0 : Vector<const char> name_string = GetScriptName(info, &name_storage, no_gc);
383 0 : LogWriteBytes(name_string.start(),
384 0 : static_cast<uint32_t>(name_string.size()) + 1);
385 : }
386 0 : char padding_bytes[8] = {0};
387 : LogWriteBytes(padding_bytes, padding);
388 : }
389 :
390 0 : void PerfJitLogger::LogWriteUnwindingInfo(Code code) {
391 : PerfJitCodeUnwindingInfo unwinding_info_header;
392 0 : unwinding_info_header.event_ = PerfJitCodeLoad::kUnwindingInfo;
393 0 : unwinding_info_header.time_stamp_ = GetTimestamp();
394 0 : unwinding_info_header.eh_frame_hdr_size_ = EhFrameConstants::kEhFrameHdrSize;
395 :
396 0 : if (code->has_unwinding_info()) {
397 0 : unwinding_info_header.unwinding_size_ = code->unwinding_info_size();
398 0 : unwinding_info_header.mapped_size_ = unwinding_info_header.unwinding_size_;
399 : } else {
400 0 : unwinding_info_header.unwinding_size_ = EhFrameConstants::kEhFrameHdrSize;
401 0 : unwinding_info_header.mapped_size_ = 0;
402 : }
403 :
404 : int content_size = static_cast<int>(sizeof(unwinding_info_header) +
405 0 : unwinding_info_header.unwinding_size_);
406 0 : int padding_size = RoundUp(content_size, 8) - content_size;
407 0 : 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 0 : if (code->has_unwinding_info()) {
413 0 : LogWriteBytes(reinterpret_cast<const char*>(code->unwinding_info_start()),
414 : code->unwinding_info_size());
415 : } else {
416 0 : OFStream perf_output_stream(perf_output_handle_);
417 0 : EhFrameWriter::WriteEmptyEhFrame(perf_output_stream);
418 : }
419 :
420 0 : 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 0 : }
424 :
425 0 : void 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 0 : CHECK(from->IsBytecodeArray());
429 0 : }
430 :
431 0 : void PerfJitLogger::LogWriteBytes(const char* bytes, int size) {
432 0 : size_t rv = fwrite(bytes, 1, size, perf_output_handle_);
433 : DCHECK(static_cast<size_t>(size) == rv);
434 : USE(rv);
435 0 : }
436 :
437 0 : void PerfJitLogger::LogWriteHeader() {
438 : DCHECK_NOT_NULL(perf_output_handle_);
439 : PerfJitHeader header;
440 :
441 0 : header.magic_ = PerfJitHeader::kMagic;
442 0 : header.version_ = PerfJitHeader::kVersion;
443 0 : header.size_ = sizeof(header);
444 0 : header.elf_mach_target_ = GetElfMach();
445 0 : header.reserved_ = 0xDEADBEEF;
446 0 : header.process_id_ = base::OS::GetCurrentProcessId();
447 : header.time_stamp_ =
448 0 : static_cast<uint64_t>(V8::GetCurrentPlatform()->CurrentClockTimeMillis() *
449 0 : base::Time::kMicrosecondsPerMillisecond);
450 0 : header.flags_ = 0;
451 :
452 : LogWriteBytes(reinterpret_cast<const char*>(&header), sizeof(header));
453 0 : }
454 :
455 : #endif // V8_OS_LINUX
456 : } // namespace internal
457 122036 : } // namespace v8
|