| // 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 |