• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/profiler/symbolizer.h"
6 
7 #include "src/execution/vm-state.h"
8 #include "src/profiler/profile-generator.h"
9 #include "src/profiler/profiler-stats.h"
10 #include "src/profiler/tick-sample.h"
11 
12 namespace v8 {
13 namespace internal {
14 
Symbolizer(CodeMap * code_map)15 Symbolizer::Symbolizer(CodeMap* code_map) : code_map_(code_map) {}
16 
FindEntry(Address address,Address * out_instruction_start)17 CodeEntry* Symbolizer::FindEntry(Address address,
18                                  Address* out_instruction_start) {
19   return code_map_->FindEntry(address, out_instruction_start);
20 }
21 
22 namespace {
23 
EntryForVMState(StateTag tag)24 CodeEntry* EntryForVMState(StateTag tag) {
25   switch (tag) {
26     case GC:
27       return CodeEntry::gc_entry();
28     case JS:
29     case PARSER:
30     case COMPILER:
31     case BYTECODE_COMPILER:
32     case ATOMICS_WAIT:
33     // DOM events handlers are reported as OTHER / EXTERNAL entries.
34     // To avoid confusing people, let's put all these entries into
35     // one bucket.
36     case OTHER:
37     case EXTERNAL:
38       return CodeEntry::program_entry();
39     case IDLE:
40       return CodeEntry::idle_entry();
41   }
42 }
43 
44 }  // namespace
45 
SymbolizeTickSample(const TickSample & sample)46 Symbolizer::SymbolizedSample Symbolizer::SymbolizeTickSample(
47     const TickSample& sample) {
48   ProfileStackTrace stack_trace;
49   // Conservatively reserve space for stack frames + pc + function + vm-state.
50   // There could in fact be more of them because of inlined entries.
51   stack_trace.reserve(sample.frames_count + 3);
52 
53   // The ProfileNode knows nothing about all versions of generated code for
54   // the same JS function. The line number information associated with
55   // the latest version of generated code is used to find a source line number
56   // for a JS function. Then, the detected source line is passed to
57   // ProfileNode to increase the tick count for this source line.
58   const int no_line_info = v8::CpuProfileNode::kNoLineNumberInfo;
59   int src_line = no_line_info;
60   bool src_line_not_found = true;
61 
62   if (sample.pc != nullptr) {
63     if (sample.has_external_callback && sample.state == EXTERNAL) {
64       // Don't use PC when in external callback code, as it can point
65       // inside a callback's code, and we will erroneously report
66       // that a callback calls itself.
67       stack_trace.push_back(
68           {FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)),
69            no_line_info});
70     } else {
71       Address attributed_pc = reinterpret_cast<Address>(sample.pc);
72       Address pc_entry_instruction_start = kNullAddress;
73       CodeEntry* pc_entry =
74           FindEntry(attributed_pc, &pc_entry_instruction_start);
75       // If there is no pc_entry, we're likely in native code. Find out if the
76       // top of the stack (the return address) was pointing inside a JS
77       // function, meaning that we have encountered a frameless invocation.
78       if (!pc_entry && !sample.has_external_callback) {
79         attributed_pc = reinterpret_cast<Address>(sample.tos);
80         pc_entry = FindEntry(attributed_pc, &pc_entry_instruction_start);
81       }
82       // If pc is in the function code before it set up stack frame or after the
83       // frame was destroyed, SafeStackFrameIterator incorrectly thinks that
84       // ebp contains the return address of the current function and skips the
85       // caller's frame. Check for this case and just skip such samples.
86       if (pc_entry) {
87         int pc_offset =
88             static_cast<int>(attributed_pc - pc_entry_instruction_start);
89         // TODO(petermarshall): pc_offset can still be negative in some cases.
90         src_line = pc_entry->GetSourceLine(pc_offset);
91         if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) {
92           src_line = pc_entry->line_number();
93         }
94         src_line_not_found = false;
95         stack_trace.push_back({pc_entry, src_line});
96 
97         if (pc_entry->builtin() == Builtin::kFunctionPrototypeApply ||
98             pc_entry->builtin() == Builtin::kFunctionPrototypeCall) {
99           // When current function is either the Function.prototype.apply or the
100           // Function.prototype.call builtin the top frame is either frame of
101           // the calling JS function or internal frame.
102           // In the latter case we know the caller for sure but in the
103           // former case we don't so we simply replace the frame with
104           // 'unresolved' entry.
105           if (!sample.has_external_callback) {
106             ProfilerStats::Instance()->AddReason(
107                 ProfilerStats::Reason::kInCallOrApply);
108             stack_trace.push_back(
109                 {CodeEntry::unresolved_entry(), no_line_info});
110           }
111         }
112       }
113     }
114 
115     for (unsigned i = 0; i < sample.frames_count; ++i) {
116       Address stack_pos = reinterpret_cast<Address>(sample.stack[i]);
117       Address instruction_start = kNullAddress;
118       CodeEntry* entry = FindEntry(stack_pos, &instruction_start);
119       int line_number = no_line_info;
120       if (entry) {
121         // Find out if the entry has an inlining stack associated.
122         int pc_offset = static_cast<int>(stack_pos - instruction_start);
123         // TODO(petermarshall): pc_offset can still be negative in some cases.
124         const std::vector<CodeEntryAndLineNumber>* inline_stack =
125             entry->GetInlineStack(pc_offset);
126         if (inline_stack) {
127           int most_inlined_frame_line_number = entry->GetSourceLine(pc_offset);
128           for (auto inline_stack_entry : *inline_stack) {
129             stack_trace.push_back(inline_stack_entry);
130           }
131 
132           // This is a bit of a messy hack. The line number for the most-inlined
133           // frame (the function at the end of the chain of function calls) has
134           // the wrong line number in inline_stack. The actual line number in
135           // this function is stored in the SourcePositionTable in entry. We fix
136           // up the line number for the most-inlined frame here.
137           // TODO(petermarshall): Remove this and use a tree with a node per
138           // inlining_id.
139           DCHECK(!inline_stack->empty());
140           size_t index = stack_trace.size() - inline_stack->size();
141           stack_trace[index].line_number = most_inlined_frame_line_number;
142         }
143         // Skip unresolved frames (e.g. internal frame) and get source line of
144         // the first JS caller.
145         if (src_line_not_found) {
146           src_line = entry->GetSourceLine(pc_offset);
147           if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) {
148             src_line = entry->line_number();
149           }
150           src_line_not_found = false;
151         }
152         line_number = entry->GetSourceLine(pc_offset);
153 
154         // The inline stack contains the top-level function i.e. the same
155         // function as entry. We don't want to add it twice. The one from the
156         // inline stack has the correct line number for this particular inlining
157         // so we use it instead of pushing entry to stack_trace.
158         if (inline_stack) continue;
159       }
160       stack_trace.push_back({entry, line_number});
161     }
162   }
163 
164   if (FLAG_prof_browser_mode) {
165     bool no_symbolized_entries = true;
166     for (auto e : stack_trace) {
167       if (e.code_entry != nullptr) {
168         no_symbolized_entries = false;
169         break;
170       }
171     }
172     // If no frames were symbolized, put the VM state entry in.
173     if (no_symbolized_entries) {
174       if (sample.pc == nullptr) {
175         ProfilerStats::Instance()->AddReason(ProfilerStats::Reason::kNullPC);
176       } else {
177         ProfilerStats::Instance()->AddReason(
178             ProfilerStats::Reason::kNoSymbolizedFrames);
179       }
180       stack_trace.push_back({EntryForVMState(sample.state), no_line_info});
181     }
182   }
183 
184   return SymbolizedSample{stack_trace, src_line};
185 }
186 
187 }  // namespace internal
188 }  // namespace v8
189