| // Copyright 2020 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/profiler/symbolizer.h" |
| |
| #include "src/execution/vm-state.h" |
| #include "src/profiler/profile-generator.h" |
| #include "src/profiler/profiler-stats.h" |
| #include "src/profiler/tick-sample.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| Symbolizer::Symbolizer(CodeMap* code_map) : code_map_(code_map) {} |
| |
| CodeEntry* Symbolizer::FindEntry(Address address, |
| Address* out_instruction_start) { |
| CodeEntry* entry = code_map_->FindEntry(address, out_instruction_start); |
| if (entry) entry->mark_used(); |
| return entry; |
| } |
| |
| namespace { |
| |
| CodeEntry* EntryForVMState(StateTag tag) { |
| switch (tag) { |
| case GC: |
| return CodeEntry::gc_entry(); |
| case JS: |
| case PARSER: |
| case COMPILER: |
| case BYTECODE_COMPILER: |
| case ATOMICS_WAIT: |
| // DOM events handlers are reported as OTHER / EXTERNAL entries. |
| // To avoid confusing people, let's put all these entries into |
| // one bucket. |
| case OTHER: |
| case EXTERNAL: |
| return CodeEntry::program_entry(); |
| case IDLE: |
| return CodeEntry::idle_entry(); |
| } |
| } |
| |
| } // namespace |
| |
| Symbolizer::SymbolizedSample Symbolizer::SymbolizeTickSample( |
| const TickSample& sample) { |
| ProfileStackTrace stack_trace; |
| // Conservatively reserve space for stack frames + pc + function + vm-state. |
| // There could in fact be more of them because of inlined entries. |
| stack_trace.reserve(sample.frames_count + 3); |
| |
| // The ProfileNode knows nothing about all versions of generated code for |
| // the same JS function. The line number information associated with |
| // the latest version of generated code is used to find a source line number |
| // for a JS function. Then, the detected source line is passed to |
| // ProfileNode to increase the tick count for this source line. |
| const int no_line_info = v8::CpuProfileNode::kNoLineNumberInfo; |
| int src_line = no_line_info; |
| bool src_line_not_found = true; |
| |
| if (sample.pc != nullptr) { |
| if (sample.has_external_callback && sample.state == EXTERNAL) { |
| // Don't use PC when in external callback code, as it can point |
| // inside a callback's code, and we will erroneously report |
| // that a callback calls itself. |
| stack_trace.push_back( |
| {FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)), |
| no_line_info}); |
| } else { |
| Address attributed_pc = reinterpret_cast<Address>(sample.pc); |
| Address pc_entry_instruction_start = kNullAddress; |
| CodeEntry* pc_entry = |
| FindEntry(attributed_pc, &pc_entry_instruction_start); |
| // If there is no pc_entry, we're likely in native code. Find out if the |
| // top of the stack (the return address) was pointing inside a JS |
| // function, meaning that we have encountered a frameless invocation. |
| if (!pc_entry && !sample.has_external_callback) { |
| attributed_pc = reinterpret_cast<Address>(sample.tos); |
| pc_entry = FindEntry(attributed_pc, &pc_entry_instruction_start); |
| } |
| // If pc is in the function code before it set up stack frame or after the |
| // frame was destroyed, SafeStackFrameIterator incorrectly thinks that |
| // ebp contains the return address of the current function and skips the |
| // caller's frame. Check for this case and just skip such samples. |
| if (pc_entry) { |
| int pc_offset = |
| static_cast<int>(attributed_pc - pc_entry_instruction_start); |
| // TODO(petermarshall): pc_offset can still be negative in some cases. |
| src_line = pc_entry->GetSourceLine(pc_offset); |
| if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) { |
| src_line = pc_entry->line_number(); |
| } |
| src_line_not_found = false; |
| stack_trace.push_back({pc_entry, src_line}); |
| |
| if (pc_entry->builtin_id() == Builtins::kFunctionPrototypeApply || |
| pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) { |
| // When current function is either the Function.prototype.apply or the |
| // Function.prototype.call builtin the top frame is either frame of |
| // the calling JS function or internal frame. |
| // In the latter case we know the caller for sure but in the |
| // former case we don't so we simply replace the frame with |
| // 'unresolved' entry. |
| if (!sample.has_external_callback) { |
| ProfilerStats::Instance()->AddReason( |
| ProfilerStats::Reason::kInCallOrApply); |
| stack_trace.push_back( |
| {CodeEntry::unresolved_entry(), no_line_info}); |
| } |
| } |
| } |
| } |
| |
| for (unsigned i = 0; i < sample.frames_count; ++i) { |
| Address stack_pos = reinterpret_cast<Address>(sample.stack[i]); |
| Address instruction_start = kNullAddress; |
| CodeEntry* entry = FindEntry(stack_pos, &instruction_start); |
| int line_number = no_line_info; |
| if (entry) { |
| // Find out if the entry has an inlining stack associated. |
| int pc_offset = static_cast<int>(stack_pos - instruction_start); |
| // TODO(petermarshall): pc_offset can still be negative in some cases. |
| const std::vector<CodeEntryAndLineNumber>* inline_stack = |
| entry->GetInlineStack(pc_offset); |
| if (inline_stack) { |
| int most_inlined_frame_line_number = entry->GetSourceLine(pc_offset); |
| for (auto entry : *inline_stack) { |
| stack_trace.push_back(entry); |
| } |
| |
| // This is a bit of a messy hack. The line number for the most-inlined |
| // frame (the function at the end of the chain of function calls) has |
| // the wrong line number in inline_stack. The actual line number in |
| // this function is stored in the SourcePositionTable in entry. We fix |
| // up the line number for the most-inlined frame here. |
| // TODO(petermarshall): Remove this and use a tree with a node per |
| // inlining_id. |
| DCHECK(!inline_stack->empty()); |
| size_t index = stack_trace.size() - inline_stack->size(); |
| stack_trace[index].line_number = most_inlined_frame_line_number; |
| } |
| // Skip unresolved frames (e.g. internal frame) and get source line of |
| // the first JS caller. |
| if (src_line_not_found) { |
| src_line = entry->GetSourceLine(pc_offset); |
| if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) { |
| src_line = entry->line_number(); |
| } |
| src_line_not_found = false; |
| } |
| line_number = entry->GetSourceLine(pc_offset); |
| |
| // The inline stack contains the top-level function i.e. the same |
| // function as entry. We don't want to add it twice. The one from the |
| // inline stack has the correct line number for this particular inlining |
| // so we use it instead of pushing entry to stack_trace. |
| if (inline_stack) continue; |
| } |
| stack_trace.push_back({entry, line_number}); |
| } |
| } |
| |
| if (FLAG_prof_browser_mode) { |
| bool no_symbolized_entries = true; |
| for (auto e : stack_trace) { |
| if (e.code_entry != nullptr) { |
| no_symbolized_entries = false; |
| break; |
| } |
| } |
| // If no frames were symbolized, put the VM state entry in. |
| if (no_symbolized_entries) { |
| if (sample.pc == nullptr) { |
| ProfilerStats::Instance()->AddReason(ProfilerStats::Reason::kNullPC); |
| } else { |
| ProfilerStats::Instance()->AddReason( |
| ProfilerStats::Reason::kNoSymbolizedFrames); |
| } |
| stack_trace.push_back({EntryForVMState(sample.state), no_line_info}); |
| } |
| } |
| |
| return SymbolizedSample{stack_trace, src_line}; |
| } |
| |
| } // namespace internal |
| } // namespace v8 |